Construyendo una experiencia de edición en línea persistente con Crystal, MongoDB (datanoise / sam0x17)

Como la documentación sigue siendo escasa, me gustaría añadir algo.

Esto es lo que estoy construyendo actualmente:

imagen

Los campos individuales se podrán editar con un editor en línea, que se guardará automáticamente en el backend, sin necesidad de recargar toda la página.

imagen

El objetivo del conjunto es un backend para mi empresa, para poder automatizar algunos procesos, calcular precios de stock, mantener cosas en stock, etc. Una aplicación de almacén básicamente, ¡a mi medida!

Tecnologías utilizadas

Partes importantes

imagen

MongoORM utiliza _id como índice principal. Tenga en cuenta el guión bajo!

El _id NO es una cadena. Es del tipo ObjectId

Por lo tanto, tendrá que ser construido. Si quieres construirlo a partir de un id anterior, utiliza este código:

mongo_id = BSON::ObjectId.new mi_cadena_id

donde my_string_id es una Cadena, y mongo_id es un ObjectId.

El código

config/base de datos.yml

es responsable de establecer la comunicación con su instancia de Mongo DB. Por favor, consulte Documentación de Sam.

stock.cr, responsable de definir Stockitem, Subitem, y los manejadores Kemal apropiados:

requiere "mongo_orm"
requieren "json"
require "./macros.cr"
requerir "./weee.cr"

módulo Stock
     incluir Macros

    class Itemgroup < Mongo::ORM::Document #utilizado para agrupar, por ejemplo, productos Pimoroni, envíos, etc.
         grupo de campos : Cadena
         has_many :stockitems #enga en cuenta que el stockitem sólo puede pertenecer a un ItemGroup a la vez.
     fin

    1TP3Esto incluye también otros artículos (envío, descargas digitales, etc.)
     class Stockitem < Mongo::ORM::Document
         campo sku : Cadena
         descripción del campo : Cadena
         campo is_alias : Bool
         campo alias_of : String 1TP3Si este campo está configurado, no utilice ninguna otra definición de este elemento, obtenga el elemento maestro aliased
         field is_set : Bool # si este elemento es un conjunto de otros elementos
         embeds_many :subitems # sólo debe establecerse si is_set = true
         campo is_physical_item : Bool
         campo weee_applicable : Bool 1TP3Puede ser: pagado por otra persona, o el artículo no es electrónico, ...
         peso del campo : Int32 # en g
         marcas de tiempo
     fin

    class Subitem < Mongo::ORM::EmbeddedDocument
         field sku : String #a referencia al artículo de stock principal. NB: esto probablemente podría ser reelaborado de una manera más agradable con belongs_to ? por ejemplo belongs_to :itemref, class_name: Stock::Stockitem
         campo nota : Cadena
         campo cantidad : Int32
     fin

    get "/stock/show" do |env|
         #https://github.com/datanoise/mongo.cr
         sis = Stockitem.all({"is_alias" => {"$eq" => false}})
         name = "Stock :: Mostrar y editar"
         mrender "stock"
     fin

    post "/stock/edit/:what" do |env|
         pp "Entrando en edición, con objetivo " + env.params.url["what"]
         pp env.params.body
         if env.params.body.has_key?("pk") && env.params.body.has_key?("value")
             pp "Inside pk & value"
             pk = env.params.body["pk"]?.as(String)
             value = env.params.body["value"]?.as(String)
             si si = Stockitem.find(BSON::ObjectId.new pk)   
                 case env.params.url["what"]
                 cuando "sku"
                     si.sku = valor
                 cuando "descripción"
                     si.descripción = valor
                 cuando "is_alias"
                     si.is_alias = valor == "true" ? true : false
                 cuando "alias_de"
                     si.alias_of = valor
                 cuando "is_set"
                     si.is_set = valor == "true" ? true : false
                 cuando "is_physical_item"
                     si.is_physical_item = valor == "true" ? true : false
                 cuando "weee_applicable"
                     pp "weee_applicable"
                     si.weee_applicable = valor == "true" ? true : false
                 fin
                
                 si.save
                     pp "dentro de si.save"
                     env.response.status_code = 200
                 si no
                     env.response.status_code = 400
                 fin
             si no
                 env.response.status_code = 400   
             fin
         si no
             env.response.status_code = 400
         fin
     fin

    #generar a partir de entradas de RAEE
     get "/stock/generate/:year" do |env|
         1TP3Recorrer toda la base de datos, recoger los elementos y mostrar una lista
         stock_collection = Hash(String,String).new
         facturas = WEEE::Invoice.all({"año"=>env.params.url["año"].to_i32})       
         facturas.each do |factura|
             #p "procesando la factura #{número de factura}"
             #enga en cuenta que es posible que no obtengamos absolutamente todas las facturas aquí, ya que easybill tiene una visión diferente de las fechas (importación)
             invoice.items.each do |item|
                 if !item.description.nil? && !item.number.nil?
                     key = item.number == "" ? item.description : item.number
                     if !key.nil?
                         if !stock_collection.has_key?(key)
                             descripción = ""
                             if !item.description.nil?
                                 descripción = item.description
                             fin                   
                             #stock_collection[key] = item.description
                             if !description.nil?
                                 stock_collection[key] = descripción
                             fin
                         end # end of ¿acervo_colección.tiene_clave?
                     end # end of if !key.nil?
                 fin
             end #end de invoice.items.each do
         end #end de invoices.each
         1TP3Comprobar que no existe ya en nuestra base de datos, si no crearla.
         stock_collection.each do |sku,description|
             if !Stockitem.find_by(:sku, sku)
                 si = Stockitem.new
                 si.sku = sku
                 si.is_alias = false
                 si.alias_of = ""
                 si.description = descripción
                 si.is_set = false
                 si.is_physical_item = true
                 si.weee_applicable = true
                 si.peso = 100
                 ¡si.save!
             fin
         fin
     end #end de la macro get

fin #end del módulo

Notas:

  • La documentación de Sam explica partes de este código, yo sólo me referiré a los escollos:
  • sis = Stockitem.all({"is_alias" => {"$eq" => false}}) - este código busca Stockitems (Mongo ORM se encargará del nombre de la BD por ti, etc.)
  • si = Stockitem.find(BSON::ObjectId.new pk)
    • nota que aquí se utiliza BSON::ObjectId.new para reconstruir el id a partir de la clave pk que se pasó
  • el código de retorno 200 en caso de éxito y 400 en caso de fallo es para que x-editable sepa si la edición ha tenido éxito

Comportamiento de si.save

  • si.save! dará un error si no es posible guardar
  • si.save devolverá true si se ha podido guardar, y false si no se ha podido

stock.ecr, la plantilla HTML

<% content_for “name” do %>
     <%= name %>
<% end %>

<% content_for “main” do%>
<div class="”col-md-12″">
     <div class="”box" box-primary”>
         <div class="box-header" with-border”>
             <h3 class="”box-title”"><%= name %></h3>
         </div>
         <div class="”box-body”">
             Tenga en cuenta que los elementos con alias no se cargan desde la base de datos.
            


                 <thead>
                     <tr role="”row”">
                         <th class="”sorting_asc”" tabindex="”0″">sku</th>
                         <th>descripción</th>
                         <th>¿es_alias?</th>
                         <th>alias_de</th>
                         <th>¿es_conjunto?</th>
                         <th>subartículos</th>
                         <th>¿es_físico_elemento?</th>
                         <th>¿wee_applicable?</th>
                         <th>peso (g)</th>
                     </tr>
                 </thead>
                 <% sis.each do |si| %>
                
                    
                    
                    
                    
                    
                    
                    
                    
                    
                
                 <% end %>

           

tbd.<A HREF="#"
                             id=".weee_applicable"
                             class="weee_applicable editable"
                             data-type="select"
                             data-pk=""
                             data-value="true"
                             data-source="[{valor: 'true', texto: 'true'}, {valor: 'false', texto: 'false'}]"
                             data-url="/stock/edit/weee_applicable"
                             data-title="RAEE: "
                         >
g

         </div>
     </div>
</div>
<script>
     //https://datatables.net/examples/styling/bootstrap
     //demo.js x-editable
     window.onload = function() {
         $(document).ready(function (){
             $("#stock_items").DataTable();
             $.fn.editable.defaults.mode = 'popup';
             $(".editable").editable();
             $(".editable").on('hidden', function(e, reason){
                 if(reason === 'save' | reason === 'nochange'){
                     var $next = $(this).closest('tr').next().find('.editable');
                     setTimeout(function(){
                         $next.editable('show');
                     }, 300);
                 }
             });
         });
     }
</script>
<% end %>

Notas:

  • ya que añadimos nuestros scripts JS después de esto (debería rehacer esto para añadir una sección de scripts personalizados al final), tenemos que envolverlos en una función window.onload - de lo contrario $ no está definido (atajo de jQuery)
  • observe cómo usamos una función aquí para saltar al siguiente editable al guardar con éxito
  • si._id.to_s.rchop - el id de BSON se traduce a una cadena, y luego se corta el último carácter extraño (¿terminador nulo?). Este es un obstáculo importante.

layout.ecr (extractos)

<!DOCTYPE html>
<html>
<head>
   <meta charset=”utf-8″>
   <meta http-equiv=”X-UA-Compatible” content=”IE=edge”>
   Dios de los Impuestos |
   <meta content=”width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no” name=”viewport”>
   <link rel=”stylesheet” href=”/bower_components/bootstrap/dist/css/bootstrap.min.css”>
   <link rel=”stylesheet” href=”/bower_components/font-awesome/css/font-awesome.min.css”>
   <link rel=”stylesheet” href=”/bower_components/Ionicons/css/ionicons.min.css”>
   <link rel=”stylesheet” href=”/dist/css/AdminLTE.min.css”>
   <link rel=”stylesheet” href=”/dist/css/skins/skin-blue.min.css”>
   <link rel=”stylesheet” href=”/danielm_uploader/css/jquery.dm-uploader.min.css”>
   <link rel=”stylesheet” href=”/bower_components/datatables.net-bs/css/dataTables.bootstrap.min.css”>
   <link rel=”stylesheet” href=”/tg_css/bootstrap-editable.css”>
   <link rel=”stylesheet” href=”/tg_css/picockpit.css”>
   <link rel=”stylesheet” href=”/tg_css/dropzone.css”> 
   <!–[if lt IE 9]>
   <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js”>
   <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js”>
   <![endif]–>

  <link rel="stylesheet"
         href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic”>
</head>
<body class="”hold-transition" skin-blue sidebar-mini”>
<div class="”wrapper”">

(...)

  <!– Content Wrapper. Contains page content –>
   <div class="”content-wrapper”">
     <!– Content Header (Page header) –>
    


       <h1>
         TaxGod ::
         Ser Dios es divertido.
       </h1>
     </section>

    <!– Main content –>
    

     <%= yield_content “main” %>

    </section>
     <!– /.content –>
   </div>
   <!– /.content-wrapper –>

(...)
</div>
<!– ./wrapper –>

<!– REQUIRED JS SCRIPTS –>

<!– jQuery 3 –>
<script src=”/bower_components/jquery/dist/jquery.min.js”></script>
<!– Bootstrap 3.3.7 –>
<script src=”/bower_components/bootstrap/dist/js/bootstrap.min.js”></script>
<!– AdminLTE App –>
<script src=”/danielm_uploader/js/jquery.dm-uploader.min.js”></script>
<script src=”/bower_components/datatables.net/js/jquery.dataTables.min.js”></script>
<script src=”/bower_components/datatables.net-bs/js/dataTables.bootstrap.min.js”></script>
<script src=”/dist/js/adminlte.min.js”></script>
<script src=”/tg_js/bootstrap-editable.min.js”></script>
<script src=”/tg_js/dropzone.min.js”></script>
</body>
</html>

Otras referencias