Search code examples
ruby-on-railsrubyactiverecordmodels

How to feed rows from one model to another?


I have 4 tables: proposals, proposal_line_items (proposal_line_items is nested inside proposals) invoices,and invoice_line_items.

I am trying to feed rows created in the proposal_line_items table into invoices when the client approves of each item. I am using a checkbox ( 1 => approved) in the proposal_line_items to determine if it is approved or not.

This is how I set up my invoices controller to make the invoices receive the rows from proposal_line_items.

class InvoicesController < ActionController
  def new
    @approved_items = Proposal.find(params[:proposal_id]).proposal_line_items.where(:approved => 1)
  end
end

When I try to create the new invoice I get the following error:

ActiveRecord::RecordNotFound in InvoicesController#new Couldn't find Proposal with 'id'= Extracted source (around line #17): 15 16 17 18 19 20 

Controller action:

# GET /invoices/new 
def new
  @approved Proposal.find(params[:proposal_id]).proposal_line_items.where(:approved => 1) 
end

Can you please help me solve this issue? Thank you!

Models:

class Proposal < ActiveRecord::Base
  has_many :proposal_line_items
end
class ProposalLineItem < ActiveRecord::Base
  belongs_to :proposal
end
class Invoice < ActiveRecord::Base
  has_many :invoice_line_items
end
class InvoiceLineItem < ActiveRecord::Base
  belongs_to :invoice
end

Migration files:

class CreateProposalLineItems < ActiveRecord::Migration
  def change
    create_table :proposal_line_items do |t|
      t.references :proposal, index: true, foreign_key: true
      t.string :name
      t.integer :approved
      t.timestamps null: false
    end
  end
end

class CreateProposals < ActiveRecord::Migration
  def change
    create_table :proposals do |t|
      t.string :name
      t.timestamps null: false
    end
  end
end

class CreateInvoiceLineItems < ActiveRecord::Migration
  def change
    create_table :invoice_line_items do |t|
      t.references :invoice, index: true, foreign_key: true
      t.string :name
      t.integer :approved
      t.timestamps null: false
    end
  end
end
class CreateInvoices < ActiveRecord::Migration
  def change
    create_table :invoices do |t|
      t.string :name
      t.timestamps null: false
    end
  end
end

routes.rb

invoices GET    /invoices(.:format)                                     invoices#index POST   /invoices(.:format)                                              invoices#create
new_invoice GET    /invoices/new(.:format)                              invoices#new
edit_invoice GET    /invoices/:id/edit(.:format)                             invoices#edit
invoice GET    /invoices/:id(.:format)                                  invoices#show
PATCH  /invoices/:id(.:format)                                          invoices#update
PUT    /invoices/:id(.:format)                                          invoices#update
DELETE /invoices/:id(.:format)                                          invoices#destroy`

proposal_proposal_line_items POST /proposals/:proposal_id/proposal_line_items(.:format)          proposals/proposal_line_items#create

  new_proposal_proposal_line_item GET    /proposals/:proposal_id/proposal_line_items/new(.:format)      proposals/proposal_line_items#new

 edit_proposal_proposal_line_item GET    /proposals/:proposal_id/proposal_line_items/:id/edit(.:format) proposals/proposal_line_items#edit

proposal_proposal_line_item GET    /proposals/:proposal_id/proposal_line_items/:id(.:format)      proposals/proposal_line_items#show

PATCH  /proposals/:proposal_id/proposal_line_items/:id(.:format)      proposals/proposal_line_items#update

PUT    /proposals/:proposal_id/proposal_line_items/:id(.:format)      proposals/proposal_line_items#update

DELETE /proposals/:proposal_id/proposal_line_items/:id(.:format)      proposals/proposal_line_items#destroy

proposals GET    /proposals(.:format)                              proposals#index

POST   /proposals(.:format)                                  proposals#create

new_proposal GET    /proposals/new(.:format)  proposals#new

edit_proposal GET    /proposals/:id/edit(.:format)                     proposals#edit

proposal GET    /proposals/:id(.:format)                             proposals#show

PATCH  /proposals/:id(.:format)                              proposals#update

PUT    /proposals/:id(.:format)                              proposals#update

DELETE /proposals/:id(.:format)                              proposals#destroy

update

controller

class InvoicesController < ApplicationController
  before_action :set_invoice, only: [:show, :edit, :update, :destroy]
  # GET /invoices
  # GET /invoices.json
  def index
    @invoices = Invoice.all
  end
  # GET /invoices/1
  # GET /invoices/1.json
  def show
  end
  # GET /invoices/new
  def new
    @approved = Proposal.require(:proposal_id).proposal_line_items.where(:approved => 1)
  end
  # GET /invoices/1/edit
  def edit
  end
  # POST /invoices
  # POST /invoices.json
  def create
    @invoice = Invoice.new(invoice_params)
    respond_to do |format|
      if @invoice.save
        format.html { redirect_to @invoice, notice: 'Invoice was successfully created.' }
        format.json { render :show, status: :created, location: @invoice }
      else
        format.html { render :new }
        format.json { render json: @invoice.errors, status: :unprocessable_entity }
      end
    end
  end
  # PATCH/PUT /invoices/1
  # PATCH/PUT /invoices/1.json
  def update
    respond_to do |format|
      if @invoice.update(invoice_params)
        format.html { redirect_to @invoice, notice: 'Invoice was successfully updated.' }
        format.json { render :show, status: :ok, location: @invoice }
      else
        format.html { render :edit }
        format.json { render json: @invoice.errors, status: :unprocessable_entity }
      end
    end
  end
  # DELETE /invoices/1
  # DELETE /invoices/1.json
  def destroy
    @invoice.destroy
    respond_to do |format|
      format.html { redirect_to invoices_url, notice: 'Invoice was successfully destroyed.' }
      format.json { head :no_content }
    end
  end
  private
    # Use callbacks to share common setup or constraints between actions.
    def set_invoice
      @invoice = Invoice.find(params[:id])
    end
    # Never trust parameters from the scary internet, only allow the white list through.
    def invoice_params
      params.require(:invoice).permit(:date, :proposal_line_item_id)
    end
end

UPDATE

New

<h1>New Invoice</h1>
<%= render 'form' %>
<%= link_to 'Back', invoices_path %>

Form

%= form_for(@invoice) do |f| %>
  <% if @invoice.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@invoice.errors.count, "error") %> prohibited this invoice from being saved:</h2>
      <ul>
      <% @invoice.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>
   <div class="field">
    <%= f.label :date %><br>
    <%= f.datetime_select :date %>
  </div>
  <div class="field">
    <%= f.label :proposal_line_item %><br>
    <%= collection_select( :proposal_line_item, :proposal_line_item_id, Proposal_line_item.all, :id, :date, {}, {:multiple => false}) %>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

Log Output

Started GET "/invoices/new" for ::1 at 2015-04-09 12:48:00 -0500
Processing by InvoicesController#new as HTML
Completed 400 Bad Request in 1ms

ActionController::ParameterMissing (param is missing or the value is empty: proposal_line_item):
  app/controllers/invoices_controller.rb:23:in `proposal_line_item'
  app/controllers/invoices_controller.rb:17:in `new'


  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/actionpack-4.2.0/lib/action_dispatch/middleware/templates/rescues/_source.erb (4.6ms)
  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/actionpack-4.2.0/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb (1.8ms)
  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/actionpack-4.2.0/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (0.6ms)
  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/actionpack-4.2.0/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb within rescues/layout (16.6ms)
  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/web-console-2.1.0/lib/web_console/templates/_markup.html (0.2ms)
  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/web-console-2.1.0/lib/web_console/templates/style.css within layouts/inlined_string (0.2ms)
  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/web-console-2.1.0/lib/web_console/templates/_inner_console_markup.html within layouts/inlined_string (0.2ms)
  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/web-console-2.1.0/lib/web_console/templates/_prompt_box_markup.html within layouts/inlined_string (0.2ms)
  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/web-console-2.1.0/lib/web_console/templates/console.js within layouts/javascript (11.0ms)
  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/web-console-2.1.0/lib/web_console/templates/main.js within layouts/javascript (0.3ms)
  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/web-console-2.1.0/lib/web_console/templates/error_page.js within layouts/javascript (0.5ms)
  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/web-console-2.1.0/lib/web_console/templates/index.html (24.2ms)


Started GET "/invoices/new" for ::1 at 2015-04-09 12:48:00 -0500
Processing by InvoicesController#new as HTML
Completed 400 Bad Request in 1ms

ActionController::ParameterMissing (param is missing or the value is empty: proposal_line_item):
  app/controllers/invoices_controller.rb:23:in `proposal_line_item'
  app/controllers/invoices_controller.rb:17:in `new'


  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/actionpack-4.2.0/lib/action_dispatch/middleware/templates/rescues/_source.erb (4.5ms)
  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/actionpack-4.2.0/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb (1.8ms)
  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/actionpack-4.2.0/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (0.6ms)
  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/actionpack-4.2.0/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb within rescues/layout (25.6ms)
  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/web-console-2.1.0/lib/web_console/templates/_markup.html (0.2ms)
  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/web-console-2.1.0/lib/web_console/templates/style.css within layouts/inlined_string (0.2ms)
  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/web-console-2.1.0/lib/web_console/templates/_inner_console_markup.html within layouts/inlined_string (0.2ms)
  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/web-console-2.1.0/lib/web_console/templates/_prompt_box_markup.html within layouts/inlined_string (0.2ms)
  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/web-console-2.1.0/lib/web_console/templates/console.js within layouts/javascript (11.0ms)
  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/web-console-2.1.0/lib/web_console/templates/main.js within layouts/javascript (0.2ms)
  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/web-console-2.1.0/lib/web_console/templates/error_page.js within layouts/javascript (0.3ms)
  Rendered /Users/davefogo/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/web-console-2.1.0/lib/web_console/templates/index.html (23.8ms)

Solution

  • The issue here is that you are making a request to a route with no parameters. I incorrectly assumed that you were seeing this error when you were submitting the form, you are seeing this error when the form is rendered. I would suggest updating the route from /invoices/new to something along the lines of /proposal/:proposal_id/invoices/new and then using this for your controller method:

    class InvoicesController < ActionController
      def new
        @approved_items = Proposal.find(params.require(:proposal_id)).proposal_line_items.where(:approved => 1)
      end
    end
    

    You can then use your original view before I suggested modifications.

    Everything below this line is wrong

    The issue here is that you are trying to query a Proposal, but the item you are actually passing in to the controller from your form is a ProposalLineItem

    On this line of your form:

    collection_select( :proposal_line_item, :proposal_line_item_id, Proposal_line_item.all, :id, :date, {}, {:multiple => false})

    You are generating a collection of ProposalLineItems for your form, but there is also a typo in that line of Proposal_line_item instead of ProposalLineItem

    In order to query a Proposal in your controller, you need to do one of two things.

    Option 1

    Update your controller to this:

    class InvoicesController < ActionController
      def new
        @approved_items = proposal ? proposal.proposal_line_items.where(approved: 1) : []
      end
    
      protected
    
      def proposal_line_item
        # The way you have your form configured, the params hash will look like this: {"proposal_line_item" => {"proposal_line_item_id" => "some_id"}}
        @_proposal_line_item = ProposalLineItem.find(params.require(:proposal_line_item).require(:proposal_line_item_id))
      end
    
      def proposal
        proposal_line_item.proposal
      end
    end
    

    Option 2

    However given the context of the form, and what your controller is trying to do, it might make more sense to render a collection of Proposals instead of ProposalLineItems. Perhaps something like this:

    # in your form
    collection_select( :proposal, :proposal_id, Proposal.all, :id, :name, {}, {:multiple => false})
    

    And then in your controller:

    class InvoicesController < ActionController
      def new
        @approved_items = proposal.proposal_line_items.where(approved: 1)
      end
    
      protected
    
      def proposal
        Proposal.find(params.require(:proposal).require(:proposal_id))
      end
    end
    

    Obviously the choice is yours here, but given what it looks like you are trying to do, I would lean towards option 2.

    Also FYI, if you want to test out the parameters stuff in a rails console, you will want to do this:

    > params = ActionController::Parameters.new
    => {}
    > params[:proposal_line_item] = {}
    => {}
    > params[:proposal_line_item][:proposal_line_item_id] = "asdf"
    => "asdf"
    > params.require(:proposal_line_item)
    => {"proposal_line_item_id"=>"asdf"}
    > params.require(:proposal_line_item).require(:proposal_line_item_id)
    => "asdf"