ruby-on-railsrubyruby-on-rails-5form-forfields-for

Nested fields_for form wont create child objects rails 5


sale.rb "Parent"

class Sale < ActiveRecord::Base
  has_many :branch_history_solds
  accepts_nested_attributes_for :branch_history_solds, :reject_if => lambda { |a| a[:content].blank? }, 
                                :allow_destroy => true
end 

class SalesController < ApplicationController
  before_action :set_sale, only: [:show, :edit, :update, :destroy]

  # GET /sales
  # GET /sales.json
  def index
    @sales = Sale.all
  end

  # GET /sales/1
  # GET /sales/1.json
  def show
  end

  # GET /sales/new
  def new
    @sale = Sale.new
    @sale.branch_history_solds.build 
  end

  # GET /sales/1/edit
  def edit
  end

  # POST /sales
  # POST /sales.json
  def create
    @sale = Sale.create(sale_params)
    # @sale.branch_history_solds.build

    respond_to do |format|
      if @sale.save
        format.html { redirect_to @sale, notice: 'Sale was successfully created.' }
        format.json { render :show, status: :created, location: @sale }
      else
        format.html { render :new }
        format.json { render json: @sale.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /sales/1
  # PATCH/PUT /sales/1.json
  def update
    @sale = Sale.find(params[:id])

    respond_to do |format|
      if @sale.update_attributes(sale_params)
        format.html { redirect_to @sale, notice: 'Sale was successfully updated.' }
        format.json { render :show, status: :ok, location: @sale }
      else
        format.html { render :edit }
        format.json { render json: @sale.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /sales/1
  # DELETE /sales/1.json
  def destroy
    @sale.destroy
    respond_to do |format|
      format.html { redirect_to sales_url, notice: 'Sale was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_sale
      @sale = Sale.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def sale_params
      params.require(:sale).permit(:receipt_no, :customer_name, :phone_number, :email, :branch_id, :paid, branch_history_sold_attributes: [:id, :sold, :branch_product_id])
    end
end

branch_history_sold.rb "Child"

class BranchHistorySold < ActiveRecord::Base
  belongs_to :sale

end

class BranchHistorySoldsController < ApplicationController

  def index
    @search = BranchHistorySold.ransack(params[:q]) 
    @branch_sold_histories = @search.result(distinct: true).group(:name).sum(:sold)
  end   

  def create 
    @sale = Sale.find(params[:sale_id]) # Find specific branch_product we will be working with
    @branch_history_sold = @sale.branch_history_solds.create(branch_history_sold_params) # Enable whitelisted attributes to get created
    flash[:notice] = "New products have been Sold from branch" # flash notice will show immediately after branch_history_sold gets created
    redirect_to branch_product_path(@branch_product) # redirect to branch_product show page
  end

  def destroy
    @sale = Sale.find(params[:sale_id]) # Find specific branch_product we will be working with
    @branch_history_sold = @sale.branch_history_solds.find(params[:id]) # Find specific branch_history_sold that will be destroyed
    @branch_history_sold.destroy # destroy branch_history_sold
    flash[:notice] = "Newly sold products have been added back to branch" # flash notice will show immediately after branch_history_sold is destroyed
    redirect_to branch_product_path(@branch_product) # redirect to branch_product show page 
  end   

  private 

  def branch_history_sold_params 
    params.require(:branch_history_sold).permit(:sold, :customer_name) # whitelisted attributes 
  end 

end

And finally my form with the fields_for attribute

<div class="container">
      <div class="row">
          <%= form_for @sale, html: { class: "form-horizontal" } do |f| %>
                <!-- Text input-->
                <div class="form-group">
                    <label class="col-md-1 control-label">R/NO</label>
                    <div class="col-md-6">
                        <%= f.collection_select(:branch_id, Branch.all, :id, :name) %>
                    </div>
                </div>
                <!-- Text input-->
                <div class="form-group">
                    <label class="col-md-1 control-label">R/NO</label>
                    <div class="col-md-6">
                        <%= f.text_field :receipt_no, placeholder: "Receipt number", class: "form-control input-md" %>
                    </div>
                </div>
                <!-- Text input-->
                <div class="form-group">
                    <label class="col-md-1 control-label" >Name</label>
                    <div class="col-md-6">
                        <%= f.text_field :customer_name, placeholder: "Prince Abalogu", class: "form-control input-md" %>
                    </div>
                </div>
                <!-- Appended Input-->
                <div class="form-group">
                    <label class="col-md-1 control-label">Number</label>
                    <div class="col-md-6">
                        <%= f.text_field :phone_number, placeholder: "08185438075", class: "form-control input-md" %>
                    </div>
                </div>
                <!-- Appended Input-->
                <div class="form-group">
                    <label class="col-md-1 control-label">E-mail</label>
                    <div class="col-md-6">
                        <%= f.text_field :email, placeholder: "example@yahoo.com", class: "form-control input-md" %>
                    </div>
                </div>

                <!-- Appended Input-->
                <%= f.fields_for :branch_history_solds, @sale.branch_history_solds.build do |b| %>
                  <div class="form-group">
                      <label class="col-md-1 control-label">Product</label>
                      <div class="col-md-6">
                        <%= b.number_field :sold, placeholder: "Quantity" %>
                      </div>
                  </div>
                    <div class="form-group">
                        <label class="col-md-1 control-label"></label>
                        <div class="col-md-6">
                            <% @branch = BranchProduct.where :branch_id, 19 %>
                            <%= b.collection_select(:branch_product_id, BranchProduct.where(branch_id: params[:branch_id] ), :id, :name ) %> Select Product
                        </div>
                    </div>
                <% end %>

                <!-- Appended Input-->
                <div class="form-group">
                    <label class="col-md-1 control-label"></label>
                    <div class="col-md-6">
                        <%= f.check_box :paid %> Paid
                    </div>
                </div>

                <!-- Button (Double) -->
                <div class="form-group">
                    <label class="col-md-1 control-label"></label>
                    <div class="col-md-8">
                        <%= f.button :submit %>
                    </div>
                </div>
          <% end %>
      </div>
    </div>

The form displays but the only problem im having now is that it doesnt create any branch_history_solds after I submit


Solution

  • The problem is that the lambda you are using to evaluate if the nested records should be rejected will always return true since your form/model does not have a content attribute:

    class Sale < ActiveRecord::Base
      has_many :branch_history_solds
      accepts_nested_attributes_for :branch_history_solds, :reject_if => lambda { |a| a[:content].blank? }, 
                                    :allow_destroy => true
    end  
    

    You're also whitelisting the wrong attributes branch_history_sold not branch_history_solds.

    You need to change this to be an attribute that is actually passed:

    class Sale < ActiveRecord::Base
      has_many :branch_history_solds
      accepts_nested_attributes_for :branch_history_solds, :reject_if => lambda { |a| a[:branch_product_id].blank? }, 
                                    :allow_destroy => true
    end  
    

    However your general setup is just plain strange and I don't know if its just the naming but it does not make much sense.

    If you want to create a point of sales system or order management system you would do it like so:

    class Order
      belongs_to :customer
      has_many :line_items
      has_many :products, through: :line_items
      accepts_nested_attributes_for :line_items,
        allow_destroy: true,
        reject_if: -> { |li| li[:product_id].blank? || li[:quantity].blank? }
    end
    
    # columns:
    # - order_id [integer, index, foreign key]
    # - product_id [integer, index, foreign key]
    # - quantity [decimal or integer]
    # - price [decimal]
    # - subtotal [decimal]
    class LineItem
      belongs_to :order
      belongs_to :product
    end
    
    class Product
      has_many :line_items
      has_many :orders, through: :line_items
    end
    

    class OrdersController
    
      def new
      end
    
    
      def create
        @order = Order.new(order_params) do
          order.customer = current_user
        end
        if (@order.save)
    
        else
    
        end
      end
    
      private
      def order_params
        params.require(:order)
              .permit(:foo, :bar, line_item_attributes: [:product_id, :quantity])
      end
    end
    

    Note that you should only let users pass an extremely limited number of params - never take things like prices from the user. The actual pricing logic should be done on the model layer. You also wan't to detach the customer details from the order model - otherwise every repeat order will duplicate data. I would spend some time getting the actual domain model down before adding additional features like search.