Before save - zapis kategorii podczas zapisu dokumentu a devise

Witam!
Chciałbym mieć możliwość zapisu kategorii (buckets) dokumentów podczas tworzenia dokumentu.

Niestety otrzymuje błąd You cannot call create unless the parent is saved
W konsoli widać jeszcze

0.3ms) BEGIN
↳ app/controllers/documents_controller.rb:21
(0.3ms) ROLLBACK
↳ app/controllers/documents_controller.rb:21
Completed 422 Unprocessable Entity in 109ms (ActiveRecord: 1.4ms)

class Document < ApplicationRecord
  mount_uploader :file, FileUploader
  delegate :user, to: :bucket
  belongs_to :bucket, required: false
  attr_accessor :new_bucket_name
  before_save :create_bucket_from_name

  def create_bucket_from_name
    create_bucket(name: new_bucket_name) if new_bucket_name.present?
  end
end

Bucket model

class Bucket < ApplicationRecord
  belongs_to :user, required: false
  has_many :documents
end

Tworzenie dokumentu

  def create
    @document = current_user.documents.new(document_params)
    if @document.save
      perform_upload_file_confirmation(@document.id)
      redirect_to dashboard_path
    end
  end

_form.html.erb


<%= form.label :bucket %>
<%= form.collection_select(:bucket_id, current_user.buckets, :id, :name, {prompt: ‘Choose a bucket’ }) %>

or create one:
<%= form.text_field :new_bucket_name %>

Ruby 2.5.1 Rails 5.2.0

Powinno być after_save albo i nawet after_create.
Czyli after_save :create_bucket_from_name

Nie bardzo rozumiem dlaczego ma być po zapisie, przecież zanim zapisze dokument muszę mieć id (jest required false ale i tak chce wykorzystac to id do dokumentu) bucket więc bucket musi być zapisany przed dokumentem …
Jeśli dobrze myślę.
Sprawdziłem też jeśli stworze od nowa dwa modele bucket i document to korzystając z tego:
https://rubyplus.com/articles/3501-Create-Model-Through-Text-Field-in-Rails-5
Można stworzyć bucket before_save podczas tworzenia Document.
W mojej aplikacji mam jeszcze Devise i user jest przypisany do dokumentu. I być może tutaj jest jakiś problem może chodzi o jakiś token tylko gdzie i jak to ominąć/naprawić

Osobiście mam jakąś awersje do callbacków i raczej stworzyłbym jakiś service object, który by wyglądał mniej więcej tak:

class DocumentService
  def self.build(user, params)
    new(user, params).build
  end

  def initialize(user, params)
    @user = user
    @params = params
  end

  def build
    @user.documents.new(params_with_bucket)
  end

  private

  def params_with_bucket
    @params.merge(bucket: bucket)
  end

  def bucket
    Bucket.find_by(id: @params[:bucket_id]) || Bucket.create(name: @params[:new_bucket_name], user: @user)
  end
end

A potem w kontrolerze takie wywołanie:

def create
  @document = DocumentService.build(current_user, document_params)
  if @document.save
    perform_upload_file_confirmation(@document.id)
    redirect_to dashboard_path
  end
end
2 Likes

Hej!
Dzięki wielkie za ten service. Działa bez zarzutu.