简体   繁体   中英

How to complete form fields automatically in Ruby On Rails

Regards! I am creating an inventory system in which, at the time of making the sale, I need to write the code or product name, I need to get the price out of it, without the need to write it manually, however, I have little knowledge of javascript, I have found several resources like autocomplete of jquery, however, I can not do it, I followed the tutorial of RailsCasts about dynamic selectors, nevertheless I need this to be achieved with text fields, I appreciate immensely someone can help me, specifically in the output model, I have the items in the output_item model, and my product model:

output_form.html.erb

<%= form_with(model: output, local: true, id: "form") do |f| %>

          <div class="form-group">
            <%= f.label :invoice %> <br>
            <%= f.text_field :invoice, class: "form-control", placeholder: "CE0001" %>
          </div>      

          <div class="form-group">
            <%= f.label :customer %> <br>
            <%= f.text_field :customer, class: "form-control", placeholder: "Ej. Jeovanny" %>
          </div>

          <table width="100%" id="output-item">
            <thead>
              <th>Product</th>
              <th>Quantity</th>
              <th>Price</th>
              <th>Total</th>
              <th>Options</th>
            </thead>
            <tbody>
              <%= f.fields_for :output_items, id: "form" do |item| %>
                <%= render "output_item_fields", f: item %>
              <% end %>
            </tbody>
          </table>

          <div class="form-group mb-0">
            <%= link_to_add_association 'Add product', f, :output_items, :"data-association-insertion-node" => "table#output-item",:"data-association-insertion-method" => "append", class: "btn btn-success" %>        
          </div>      

      <div class="form-group>
          <%= link_to "Cancelar", outputs_path, class: "btn btn-danger" %>        
          <%= f.submit "Send", class: "btn btn-primary" %>  
      </div>        

<% end %>

output_items_fields.html.erb

<tr class="item">
    <td><%= f.text_field :product_id, class: "form-control field" %></td>
    <td><%= f.text_field :quantity, class: "form-control field quantity" %></td>
    <td><%= f.text_field :price, class: "form-control field price" %></td>
    <td><input type="text" class="form-control field subtotal"></td>
    <td class="text-center">
        <%= link_to_remove_association f, { wrapper_class: "item", class: "btn btn-danger" } do %>
            <i class="fal fa-trash-alt"></i>
        <% end %>
    </td>
</tr>

product.rb

class Product < ApplicationRecord

    has_many :input_items
    has_many :output_items
    validates :code, :name, presence: true

    def purchase
        input_items.pluck(:quantity).sum
    end

    def sale
        output_items.pluck(:quantity).sum
    end

    def stock
        (input_items.pluck(:quantity).sum - output_items.pluck(:quantity).sum)
    end

    def price
        self.input_items.sum(:price) / self.input_items.count
    end

end

Output.rb

class Output < ApplicationRecord

    has_many :output_items, inverse_of: :output, :dependent => :delete_all
    accepts_nested_attributes_for :output_items, reject_if: :all_blank, allow_destroy: true    

    validates :invoice, :provider, presence: true

end

output_item.rb

class OutputItem < ApplicationRecord

    belongs_to :product
    belongs_to :output
    validates :quantity, :price, numericality: true, presence: true  

end

class Product < ApplicationRecord

    has_many :output_items

end

input.rb

class Input < ApplicationRecord

    has_many :input_items, inverse_of: :input, :dependent => :delete_all
    accepts_nested_attributes_for :input_items, reject_if: :all_blank, allow_destroy: true    

    validates :invoice, :provider, presence: true

end

input_item.rb

class InputItem < ApplicationRecord

  belongs_to :product
  belongs_to :input
  validates :quantity, :price, numericality: true, presence: true

end

input_migrate.rb

class CreateInputs < ActiveRecord::Migration[5.2]
  def change
    create_table :inputs do |t|
      t.string :invoice
      t.string :provider

      t.timestamps
    end
  end
end

input_items_migration.rb

class CreateInputItems < ActiveRecord::Migration[5.2]
  def change
    create_table :input_items do |t|
      t.integer :product_id
      t.float :quantity
      t.float :price
      t.belongs_to :input, foreign_key: true

      t.timestamps
    end
  end
end

product_migrate.rb

class CreateProducts < ActiveRecord::Migration[5.2]
  def change
    create_table :products do |t|
      t.string :code
      t.string :name
      t.float :utility

      t.timestamps
    end
  end
end

output_migrate.rb

class CreateOutputs < ActiveRecord::Migration[5.2]
  def change
    create_table :outputs do |t|
      t.string :invoice
      t.string :customer

      t.timestamps
    end
  end
end

output_item_migrate.rb

class CreateOutputItems < ActiveRecord::Migration[5.2]
  def change
    create_table :output_items do |t|
      t.integer :product_id
      t.float :quantity
      t.float :price
      t.belongs_to :output, foreign_key: true

      t.timestamps
    end
  end
end

The code that RailsCast offers, to autocomplete selectors is with:

(function() {
    jQuery(function() {
      var states;
      $('#input_price').parent().hide();
      states = $('#input_price').html();
      return $('#input_product_id').change(function() {
        var country, escaped_country, options;
        country = $('#input_product_id :selected').text();
        escaped_country = country.replace(/([ #;&,.+*~\':"!^$[\]()=>|\/@])/g, '\\$1');
        options = $(states).filter("optgroup[label='" + escaped_country + "']").html();
        if (options) {
          $('#input_price').html(options);
          return $('#input_price').parent().show();
        } else {
          $('#input_price').empty();
          return $('#input_price').parent().hide();
        }
      });
    });

  }).call(this); 

In what way could adapt, or what is the most convenient way to do this, as an added data, I use cocoon for dynamic items, I appreciate immensely, can help me, Regards!


Updates

output_items_fields.html.erb

<tr class="item">
    <td><%= f.text_field :product_id, class: "form-control field" %></td>
    <td><%= f.text_field :quantity, class: "form-control field quantity" %></td>
    <td><%= f.text_field :price, class: "form-control field price" %></td>
    <td><input type="text" class="form-control field subtotal"></td>
    <td class="text-center">
        <%= link_to_remove_association f, { wrapper_class: "item", class: "btn btn-danger" } do %>
            <i class="fal fa-trash-alt"></i>
        <% end %>
    </td>
</tr>

<% content_for :javascript do %> 
  <script type="text/javascript">var product_info = $.parseJSON('<%= @product_info.to_json.html_safe %>');</script>
<% end %>

<% content_for :javascript do %> 
    <script type="text/javascript">
      var product_info = $.parseJSON('<%= @product_info.to_json.html_safe %>');
      (function() {
          jQuery(function() {
            var product = product_info[$('#output_product_id')]
            return $('#output_product_id').change(function() {
            if (product) {                     
              $('#output_price').val(product.price);
              $('#output_description').val(product.description);
            } else {
              $('#output_price').val("");
              }
            });
          });
        }).call(this); 
    </script>
<% end %>

outputs_controller.rb

class OutputsController < ApplicationController
  before_action :set_output, only: [:show, :edit, :update, :destroy]

  def new
    @output = Output.new
    @output.output_items.build
    @product_info = Product.joins(:input_items).select(:price).all.inject({}) {|a, b| a[b.input_items] = {price: b.price}}  
  end
end

When I look for the product_info and testing in the browser console, I get it:

console.log(product_info)
Object { price: null }
debugger eval code:1:1
undefined

console.log('testing')
testing debugger eval code:1:1
undefined

As long as you don't have a very large number of products, this is how I would tackle this to auto populate based on model number (if I understand your requirements correctly). You essentially need to get a Javascript representation of all of your product data, then feed that to jQuery to do the populating, similar to how the Railscast was using country name. And lucky for you, you can easily convert a ruby Hash or Array in to something Javascript can see using .to_json.

First, create a hash that has a key of product model to product price, and populate it from your data source:

@product_info = Product.select(:code,:price).all.inject({}) {|a, b| a[b.code] = {price: b.price}}

And put that in your controller action.

Next, get that instance variable in to a Javascript variable on your page. This part depends on if you are set up to use content_for :javascript - see Including inline javascript using content_for in rails .

<% content_for :javascript do %> 
  <script type="text/javascript">
    var product_info = $.parseJSON(<%= @product_info.to_json.html_safe %>);
  </script>
<% end %>

The finally, add this take on the Railscast Code to the content_for block and make it look like the following (replacing the old content_for :javascript block). This part is up to you - if you want a text box that is just populated, or if you want what they had, where you hide and show the field if you can find the values in the hash. I just made it populate it if it could find it, or make it a blank string if it couldn't

<% content_for :javascript do %> 
    <script type="text/javascript">
      var product_info = $.parseJSON(<%= @product_info.to_json.html_safe) %>;
      (function() {
          jQuery(function() {
            return $('#input_product_id').change(function() {
            if (var product = product_info[$('#input_product_id')]) {
              $('#input_price').val(product.price);
            } else {
              $('#input_price').val("");
              }
            });
          });
        }).call(this); 
    </script>
<% end %>

Issues / Alternatives

Now this would work decently as a first iteration, but there are some issues with it that may apply to you, depending on how you are set up.

The more products you have, the more data you have to spit out as a JSON string. If that string becomes 2, 4, 10 megabytes, it will really slow your users down as they have to load that data just to view your page. You can mitigate that by going a different route and firing off an AJAX call to get the product data as the user types in the model number, and populate the fields as we're doing here. It's not that much more work to go that route as long as you're OK with having an endpoint that gets hit every time the user types something in a text field, and the corresponding database call it needs to make to do the searching.

The other issue is that every time the page loads, you will be re-fetching all of your product data. You would need to start thinking about caching that data either in the view, or in the controller, so that you always load the product model->price/description map from cache instead of fetching it from the database.

Finally, if some other user is entering product information which you want available to power your auto populate code, it would require a page reload. If you don't have that many products being added/changed, this isn't as big of a concern, but if it changes multiple times per minute/hour, you may want to think about going with the aforementioned solution which fires an AJAX call every time the user types something in, so you can be guaranteed of the latest Product information being returned, rather that some potentially stale representation of all of your product information.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM