Logical problems when adding dynamic fields - Ruby On Rails

0

In a purchase form, I add the product plus the requisite data, however, I need that purchase form to accept more than one product in it, this could be achieved using nested form + the coconut gem , however, the attributes belong to the same model, so the fastest logic that comes to me is to create 2 different models, to then be replicating the fields, however, I do not know if there is a way to replicate those fields without the need to create a separate model that will only serve me to nest them later, I share the essential code of the problem:

Purchase form

<div id="form-modal" class="modal" tabindex="-1" role="dialog">
  <div class="modal-dialog modal-lg" role="document">
    <div class="modal-content">

      <div class="modal-header">
        Producto      
      </div>

      <%= form_with(model: @input, remote: true) do |form| %>

        <div class="modal-body">
          <div class="row">
            <!-- Errors -->
            <div class="col-sm-12">
              <% if @input.errors.any? %>
                <div id="error_explanation">
                  <h2><%= pluralize(@input.errors.count, "error") %> prohibited this input from being saved:</h2>
                  <ul>
                  <% @input.errors.full_messages.each do |message| %>
                    <li><%= message %></li>
                  <% end %>
                  </ul>
                </div>
              <% end %>
            </div>
            <!-- Fields -->  
            <div class="col-sm-12">
              <div class="form-group">
                <% form.label :invoice %>
                <%= form.text_field :invoice, placeholder: "Codigo de la factura", class: "form-control" %>    
              </div>
            </div>
            <div class="col-sm-6">
              <div class="form-group">
                <% form.label :product_id %>
                <%= form.collection_select :product_id, Product.all, :id, :code, { :include_blank => "Seleccionar producto" }, required: true, class: "form-control" %>
              </div>
            </div>
            <div class="col-sm-6">
              <div class="form-group">
                <% form.label :quantity %>
                <%= form.text_field :quantity, placeholder: "Cantidad", class: "form-control" %>
              </div>
            </div>
            <div class="col-sm-6">
              <div class="form-group">
                <% form.label :precio %>
                <%= form.text_field :price, placeholder: "Precio", class: "form-control" %>                    
              </div>
            </div>
            <div class="col-sm-6">
              <div class="form-group">
                <% form.label :utilidad %>
                <%= form.text_field :utility, placeholder: "Porcentaje de utilidad", class: "form-control" %>      
              </div>
            </div>
            <div class="col-sm-6">
              <div class="form-group">
                <% form.label :customer %>
                <%= form.text_field :provider, placeholder: "Proveedor", class: "form-control" %>
              </div>
            </div>
          </div>
        </div>
        <div class="modal-footer">
          <%= form.submit "Enviar", class: "btn btn-primary" %>
        </div>

      <% end %>

    </div>
  </div>
</div>

Buy Model (Input)

class Input < ApplicationRecord

    belongs_to :product

    validates :quantity, numericality: true
    validates :invoice, uniqueness: true

end

Model Products

class Product < ApplicationRecord

    has_many :inputs

end

Migrate Purchase

class CreateInputs < ActiveRecord::Migration[5.2]
  def change
    create_table :inputs do |t|
      t.string :invoice
      t.integer :product_id
      t.float :quantity
      t.float :price
      t.float :utility
      t.string :provider

      t.timestamps
    end
  end
end

Product migration

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

      t.timestamps
    end
  end
end

Basically I need to replicate all the fields of purchase (Input), except for the invoice number (Invoice), however as I mention again, all these attributes belong to the same model, so I do not know in what way I should handle This is not to have 2 different models that are nested, I appreciate your suggestions

    
asked by Hector Hernandez 08.08.2018 в 00:30
source

1 answer

0

I do not understand very well what you are trying to do with the dynamic fields, but I will share with you how I work.

If you use postgres you can use a field like jsonb type. In your migration (in this case projects) would go like this:

class AddColumnCamposToProyecto < ActiveRecord::Migration[5.1]
  def change
    add_column :proyectos, :campos, :jsonb #, null: false, default: {}
    add_index :proyectos, :campos, using: :gin
  end
end

Then to make them dynamically create you can use vue js, I use it with rails 5 and webpack this is my code in vue js to add and remove fields dynamically

this is created in: app / javascript / components / multi-text.vue

<template>
  <div class="multi-text">
  <h5>Campos customizables</h5>
  <p>Hacer click sobre los (label) para editar el nombre de el campo </p>
    <form >
      <div class="form-inputs">  
        <div class="row" v-for="(campo, index) in campos" :key="index">
            <div class="col-sm-2">
              <label v-show = "campo.editable == false" @click = "campo.editable = true"> {{campo.nombre}}</label>
              <input v-show="campo.editable == true"
                type="text"
                placeholder="valor" 
                class="form-control" 
                v-model="campo.nombre"
                v-on:keyup = "sinCaracteresEspeciales($event,campo)"
                v-on:blur= "campo.editable=false;campo.soltoTecla=true"

              />
            </div>
             <div class="col-sm-2 ">
              <input 
                 type="text"
                 placeholder="valor" 
                 class="form-control" 
                 v-model="campo.valor"
              />
            </div>

           <div class="col-sm-2">
              <button
                type="button"
                class="btn-sm btn-danger"
                title = "Quitar"
                @click.prevent="deleteValue(index)"
                v-show="numeroCampos > 1"
                >
              <i class="fa fa-trash"></i>
            </button>
           </div>
        </div>
        <div class="row">
          <div class="col-sm-2">
            <button
              type="button"
              class="btn-sm btn-primary"
              title = "Añadir"
              @click.prevent="addValue"
              >
              <i class="fa fa-plus"></i> 
            </button>
             <button
               type="button"
               class="btn-sm btn-primary"
               title ="Guardar"
               @click.prevent="guardarCambios"
            >
              <i class="fa fa-save"> Guardar</i>
            </button>
          </div>
        </div>
      </div>
    </form> 
      <!--  <pre> {{ $data | json }} </pre>  -->
  </div>
</template>

<script>
  import axios from 'axios';

  export default {
    props: ['rutaSaveCampos','camposProyecto'],
    // return empty data until init complete async
    data() {
      return {    
          campos: [{nombre: "(label)", valor: "", editable: false,soltoTecla: false}],
          errors: [],
          resultado: ""
        };
    },
    created(){
      if (this.camposProyecto) {
        const campos_de_proyecto = JSON.parse(this.camposProyecto);
        this.campos = campos_de_proyecto.lista;
      }
    },
    computed: {
      numeroCampos: function(){
        return this.campos.length;
      }
    },
    methods: {
      deleteValue: function(index) {
        this.campos.splice(index, 1);
        //this.$emit('input', this.campos);
      },
      addValue: function() {
        this.campos.push( { nombre: '(label)', valor: '', editable: false, soltoTecla: false} );
        //this.$emit('input', this.campos);
      },
      guardarCambios: function(){
        const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
        axios.post(this.rutaSaveCampos,{
          dataCampos: this.campos,
          authenticity_token: token
        }).then(response => {
           this.resultado= response.data;
           if (this.resultado == "ok") {
             toastr.success("Campos Guardados"); 
           }

         })
        .catch(e => {
          this.errors.push(e);
          toastr.error(e);
        })
      },
      sinCaracteresEspeciales(event,campo){
        //reemplaza caracters especiales a excepcion de numeros, letras, espacio o ñ y Ñ
        //event.target.value = event.target.value.replace(/[^ña-zA-ÑZ0-9 ]/g, '')
        campo.nombre =event.target.value.replace(/[^ña-zA-ÑZ0-9 ]/g, '')
      }    
    }

  }
</script>

Then if you are using turbolinks 5 with vue you have an adapter in your code located in app / javascript / packs / multi-text.js I would go this

import Vue from 'vue/dist/vue.esm'
import MyComponent from '../app.vue'
import MultiText from '../components/multi_text.vue'

/*
Si quiere tener como acceso global o seteas en la variale windo
window.Vue = Vue;
window.MultiText = MultiText;
*/

    import TurbolinksAdapter from 'vue-turbolinks';
    Vue.use(TurbolinksAdapter)
    document.addEventListener('turbolinks:load', () => {
      var element= document.getElementById("este");
      if (element!=null) {
        var vueapp = new Vue({
          el: element,
          components: { MultiText}
        });
      }
    });

Then in your view you would invoke it like that

 <div id="este">
      <multi-text 
        ruta-save-campos="<%= guardar_campos_proyectos_path(@proyecto) if @proyecto.id.present? %>"
        campos-proyecto= "<%= @proyecto.campos.to_json if @proyecto.campos.present? %>" > 
      <multi-text/> 
    </div>

And finally the controller to save your fields would have a method like this:

def guardar_campos
    data_campos = params[:dataCampos].to_json(:only =>[:nombre, :valor])
    hash_data = Hash.new
    # Añadimos el array de los datos a el hash 
    hash_data = {"lista": JSON.parse(data_campos).to_a}

    if @proyecto.update_attributes(campos: hash_data )
      render json: :ok
    else
      render json: @proyecto.errors
    end
    authorize @proyecto
  end

I hope it helps you.

    
answered by 10.08.2018 в 00:20