There is a model Bike which has several images through the has_many association : pictures

Task: when saving a bike, get its old number of pictures and compare it with the new one.

I tried to use this option

class Bike < ActiveRecord::Base attr_accessor :old_pictures_size has_many :pictures, dependent: :destroy accepts_nested_attributes_for :pictures, allow_destroy: true before_save do @old_pictures_size = pictures.size end before_update :drop_moderation private def drop_moderation if title_changed? || description_changed? || pictures_changed? self.status = :on_moderation end end def pictures_changed? @old_pictures_size > pictures.size end end 

but in the @old_pictures_size variable is still a new number of pictures.

  • old is what? Do you also update the pictures when saving? - Mal Skrylev
  • Before saving pictures. There is a form where a bike is added and pictures to it through nested_forms. The user can add or remove pictures from the bike. The task of saving a bike is to find out how many pictures were there before saving and compare it with the number of pictures after saving - Pavel Scheglov
  • @PavelScheglov, just in case, the controller code can also be added to the question. - anoam
  • @PavelScheglov, that is, do you still have accept_nested_attibutes for pictures? - Mal Skrylev
  • I can jump to where to dig, if you also update links (ie pictures) using nested_attributes, try to callback before recording to see the set :pictures , as I understand it, although it is recorded after the main record, but everything - should be urinated to upgrade, i.e. write :pictures on the idea should be changed: true . Ie it will lead accordingly to the pictures_changed?: true - Mal Skrylev

2 answers 2

If your pictures are updated before the object of this model, then just set the supervision if at least one record of the picture is newer than this record itself. You can do something like this check:

 def pictures_changed? if self.new_record? pictures.any? else pictures.any? { |pic| pic.updated_at > self.updated_at } end end 
  • And how will this work for an unsaved object? - anoam
  • @anoam something like that, thanks for the reminder =) - Mal Skrylev
 before_save do @old_pictures_size = pictures.size end 

Well, the value is calculated before saving. If up to this point were added (but not saved) images for example via bike.pictures << Picture.new) then they are already in the collection and will be counted.

The easiest (but not the most correct) is to move this into after_initialize .

I also want to note that a comparison by the number of pictures is not the best way to check whether they were added. Example: I create a Bike , then edit it, delete the old image and add a new one. The number of pictures has not changed, but you need to moderate it again.

In addition, callbacks and nested attributes are very difficult to control and maintain. More correctly, in this case, use FormObject . And Virtus can help create such objects. I cannot make a prototype for a specific case - there is too little data (you need an understanding of what is happening in Picture , the controller and how else you plan to use old_pictures_size ). Here is an example of such an object for the general case.

UPD

We found out that the pictures are added separately. Here is FormObject 's example:

 class BikeForm include ActiveModel::Validations include ActiveModel::Conversion include Virtus.model(constructor: false) attribute :id, Integer, writer: :private attribute :title, String attribute :description, String attribute :picture_ids, Array[Integer] # дефолтное значение взял с потолка. Главное чтобы не было moderation attribute :status, String, writer: :private, default: "confirmed" def initialize(bike = nil) set_default_attributes return unless bike self.attributes = bike.slice(:title, :description, :picture_ids) self.id = bike.id end # Аналогичные сеттеры можно переопределить и для других аттрибутов def picture_ids=(new_picture_ids) self.status = :moderation super end def save return false unless valid? bike.attributes = attributes bike.save end private def bike @bike ||= id ? Bike.find(id) : Bike.new end end 

In this way, we get rid of callbacks, when the significant attributes change, the object will automatically be sent to moderation.

  • Rummaging deeper into my code, I found that the pictures are added using ajax and my own pictures controller with the upload method :) That is, the Bike models in Bike will not help at all, since the user can download and delete an arbitrary number of pictures without saving the bike model. In general, I apologize for the confusion, now I will update the question. - Pavel Scheglov
  • @PavelScheglov, but when you save the form, at a minimum, new id will mingle. Now I’ll add FormObject 's example to my answer. - anoam