How to add values obtained from the same field?

0

I would like to calculate the values of a specific field in my table item_compras

create_table "item_compras", force: :cascade do |t|
    t.integer  "cantidad_bidon"
    t.integer  "total_bidones"
end

I am using accepts_nested_attributes_for :item_compras , in the model Compra , so in the shopping form I can enter more than one item_compra .

Suppose I enter a item_compra that has 5 in cantidad_bidon and another has 4 , which as a result should be saved in the field total_bidones giving a total of 9 drums, but if only entry a item_compra , the item must be saved without performing any operation. Can I do this with a after_create ?

EDITING

I'm using after_create :total_bidones_compra in model ItemCompra

def total_bidones_compra suma = self.cantidad_bidon + self.cantidad_bidon self.total_bidones = suma self.save end

In this way I would only be making the sum of only 2 items, but if I enter more than this amount, it does not keep adding up.

In the Compras controller, I simply add the model attributes ItemCompra

def compra_params
  params.require(:compra).permit(..., 
    item_compras_attributes: [:id, :cantidad_bidon, :product_id, :calibre_id, :kg_bidon, :precio_kg, :_destroy ])
end

EDIT N ° 2

Action of ComprasController

  def new
    @compra = Compra.new
    5.times { @compra.item_compras.build }
  end


  def create
   @compra = Compra.new(compra_params)
   respond_to do |format|
    if @compra.save
      format.html { redirect_to @compra, notice: 'La compra ha sido creada exitosamente.' }
      format.json { render :show, status: :created, location: @compra }
    else
      format.html { render :new }
      format.json { render json: @compra.errors, status: :unprocessable_entity }
    end
  end
end
    
asked by Mosiah Ricardo 22.05.2017 в 16:41
source

1 answer

1

I would do it in the controller, adding all the values of :cantidad_bidon and adding them to the object @compra before saving it.

You could do it directly in your action create or, better yet, generate an additional method to do it (and I put it as private):

private

def actualiza_total_bidones!
  items = params[:compra][:item_compras_attributes]
  total = items.values.map { |item| item[:cantidad_bidon].to_i }.reduce(:+)

  @compra.item_compras.each { |item| item.total_bidones = total }
end

This method is to obtain all the values of cantidad_bidon for each item received and add them to obtain the total; once you have the total then iterate in each item created in @compra updating the attribute total_bidones with the total obtained.

Now just call the method in your action create after creating the object @compra but before saving it:

def create
  @compra = Compra.new(compra_params)
  actualiza_total_bidones!             # <-- Agrégalo aquí

  respond_to do |format|
    // ...
  end
end

In this way, you will update with any number of items that your controller receives.

To complement the answer considering your comment

  

... calculate other values of the same model, for example: quantity_bun *   total_kg.

The logic remains the same, in this case you need to calculate total_kg (which I imagine is the sum of the parameter kg_bidon of each item, in that case you would calculate total_kg in the same way as total_bidones but changing the cantidad_bidon parameter to kg_bidon :

total = items.values.map { |item| item[:kg_bidon].to_i }.reduce(:+)

But instead of repeating the previous method, now is a good time to separate it into two methods, thus:

# Devuelve la suma del valor del atributo dado en todos los items de la compra
def obtiene_total(atributo)
  items = params[:compra][:item_compras_attributes]
  items.values.map { |item| item[atributo].to_i }.reduce(:+)
end

# Actualiza el atributo 'total_bidones' en cada ítem de la compra.
def actualiza_total_bidones!(total_bidones)
  @compra.item_compras.each { |item| item.total_bidones = total_bidones }
end

And now we can add a new method to update the new field (I do not have the name so I will use total_kg_bidones ):

# Actualiza el atributo 'total_kg_bidones' en cada ítem de la compra.
def actualiza_total_kg!(total_kg)
  @compra.item_compras.each { |item| item.total_kg_bidones = item.cantidad_bidon * total_kg  }
end

Finally, you should only call the methods in the action create of the controller:

def create
  @compra = Compra.new(compra_params)
  actualiza_total_bidones!(obtiene_total(:cantidad_bidon))
  actualiza_total_kg!(obtiene_total(:kg_bidon))

  respond_to do |format|
    // ...
  end
end

Following this logic you can continue adding calculations, you simply iterate all the items that you have received in params when you need summations (or any operation that involves all the items) or, doing direct operations between the attributes of each item.

This code works, but it would be a good refactoring exercise:

  • Change the obtiene_total method to be able to get all the totals that you require a single round (instead of iterating params for each total).
  • Move the logic of actualiza_total_bidones and actualiza_total_kg to your model ItemCompra .
answered by 25.05.2017 / 14:34
source