Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I iterate over a collection of objects, one at a time being rendered in a view, without using a multi-step form?

I have a collection of objects. What I would like to do is iterate over the entire collection, but show each object on a page/view by itself, and allow the user to interact with each object individually. Ideally, I would prefer not to use a multi-part form if I can avoid it, for reasons I spell out at the end of my question.

I am trying to implement screens like the images below.

Screen 1 Screen 2

Basically the user of the app, will go to a location to do a reconciliation of inventory (of each product) in that location. That's what the screens are showing. For each product, they have to update the inventory.

The summary & requirements are as follows:

  • A location has_many inventory_items.
  • A user begins a reconciliation whenever they want to do an inventory check.
  • A reconciliation habtm inventory_items && belongs_to :location.
  • An inventory_item habtm reconciliations && belongs_to :location.
  • I can't predict in advance how many inventory_items there are.
  • There could be dozens or hundreds of inventory_items.
  • I can break up the inventory_items into different groups if the number becomes unwieldy....similar to a pagination approach.

So my models look like this:

Reconciliation

# == Schema Information
#
# Table name: reconciliations
#
#  id          :integer          not null, primary key
#  location_id :integer
#  created_at  :datetime         not null
#  updated_at  :datetime         not null
#

class Reconciliation < ApplicationRecord
  belongs_to :location
  has_and_belongs_to_many :inventory_items
end

Location

# == Schema Information
#
# Table name: locations
#
#  id         :integer          not null, primary key
#  name       :string
#  created_at :datetime         not null
#  updated_at :datetime         not null
#

class Location < ApplicationRecord
  has_and_belongs_to_many :products
  has_many :inventory_items, inverse_of: :location
  accepts_nested_attributes_for :inventory_items
  has_many :reconciliations
end

Inventory Item

# == Schema Information
#
# Table name: inventory_items
#
#  id                    :integer          not null, primary key
#  product_id            :integer
#  location_id           :integer
#  quantity_left         :integer
#  quantity_delivered    :integer
#  quantity_on_hand      :integer
#  date_of_last_delivery :datetime
#  created_at            :datetime         not null
#  updated_at            :datetime         not null
#

class InventoryItem < ApplicationRecord
  belongs_to :product
  belongs_to :location, inverse_of: :inventory_items
  has_and_belongs_to_many :reconciliations
end

Here is my inventory_items_reconciliations Join Table:

  create_table "inventory_items_reconciliations", id: false, force: :cascade do |t|
    t.bigint "inventory_item_id", null: false
    t.bigint "reconciliation_id", null: false
    t.index ["inventory_item_id", "reconciliation_id"], name: "index_inventory_item_id_reconciliation_id_join"
    t.index ["reconciliation_id", "inventory_item_id"], name: "index_reconciliation_id_inventory_item_id_join"
  end

My routes.rb:

  resources :locations, shallow: true do
    resources :inventory_items
    resources :reconciliations
  end

My ReconciliationsController#New:

  def new
    @location = Location.find(params[:location_id])
    @reconciliation = @location.reconciliations.new
    @inventory_items = @location.inventory_items
    @num_of_inventory_items = @inventory_items.coun
  end

My app/views/reconciliations/new.html.erb:

<% @inventory_items.each do |inventory_item| %>
  <%= render 'form', reconciliation: @reconciliation, inventory_item: inventory_item %>
<% end %>

My app/views/reconciliations/_form.html.erb:

<%= simple_form_for @reconciliation, url: :location_reconciliations do |f| %>
  <%= f.error_notification %>

  <strong>Name</strong>: <%= inventory_item.product.name %> <br />
  <strong>Quantity Left:</strong> <%= inventory_item.quantity_left %> <br />
  <strong>Quantity Delivered:</strong> <%= inventory_item.quantity_delivered %> <br />

  <div class="form-actions">
    <%= f.button :submit, "Update", class: "btn btn-primary" %>
  </div>
<% end %>

What this does is just displays all of the location.inventory_items on that reconciliation page, when all I want is for 1 to be displayed.

So, what I would like to do is this:

  • Get the collection of inventory_items in the location that the user has chosen.
  • Begin iteration over that collection and show the user each object, in their own view, one at a time.
  • As the user progresses (i.e. once they press 'Next'), essentially mark that inventory_item as reconciled even if the user didn't update the quantity (i.e. say there have been no sold since it was delivered).
  • Once all inventory_items in this collection are iterated over, then save the reconciliation record to the database that accurately reflects the quantity information for each inventory_item within this reconiliation cycle.

I looked at the Wicked Gem, but it seems that I need to be able to statically declare the number of steps in advance. As you can see above, if each inventory_item in my collection is a step, there needs to be a dynamic number of steps.

I also came across similar constraints with other multi-step wizard approaches.

How do I achieve what I am trying to do?

like image 992
marcamillion Avatar asked Mar 24 '18 09:03

marcamillion


1 Answers

What is a reconciliation?

i) “A procedure for confirming that the balance in a chequebook matches the corresponding bank statement. This is normally done by preparing a bank reconciliation statement.

ii) A procedure for confirming the reliability of a company’s accounting records by regularly comparing [balances of transactions]. An account reconciliation may be prepared on a daily, monthly, or annual basis.”

user has_many items

warehouse has_many accounts

accounts has_many items

warehouse has_many items, throught: :accounts

item belongs_to warehouse

class Product
   has_many :items
   accepts_nested_attributes_for :items
end

class Item
   belongs_to :product
end

class Account
   has_many :products
   has_many :warehouses, through: :products
end

class Warehouse 
   has_many :products
   has_many :accounts, through: :products
end

Your routes.rb

resources :warehouses do 
    resources :products
    resources :accounts
end

resources :accounts do 
    resources :products
    resources :warehouses
end

resources :products do 
    resources :items
end

The account model will have a reconciled field.

This is your form. It is displaying a Product.

enter image description here

form_for @product do |f|
   <!-- your fields: account_id and number of items--> 
   f.fields_for :items do |item|
     <!-- your item -->
   end
   f.submit
end

ProductsController#update

def update
    warehouse = Warehouse.find(params[:warehouse_id])
    @product = warehouse.products.first
    # needs to be refactored
    if @product.update && @product.is_the_last_one
       redirect_to :your_choosen_root
    elsif @product.update 
       redirect_to edit_product_path(@product.next)
    else
       render :edit
    end
end
    

Once all inventory_items in this collection are iterated over, then save the reconciliation record to the database that accurately reflects the quantity information for each inventory_item within this reconiliation cycle.

I'll think about it tomorrow (it's 22:55) :)

like image 168
Fabrizio Bertoglio Avatar answered Oct 12 '22 13:10

Fabrizio Bertoglio