I wrote it very carefully, so it has a lot of volume. Skip what you know: thumbs up:
-I want to operate multiple models without using accepts_nested_attributes_for.
・ I want to update not only new data.
A method that allows you to update the records of the associated model at once.
Example) When sending a message with multiple images attached

In the above case, you can save the message and the image associated with it. It's convenient. However, ** Actually, this method is not very popular **: joy: (Click here for details)
Therefore, I will introduce alternative form objects.
A separate class for processing forms, separated from the model.
It allows you to update multiple records without ** accepts_nested_attributes_for. ** **
If you want to know more about other merits and principles, please see this article. The explanation is insanely easy to understand: pray:
Implement a function that allows the user (parent) to associate a message (child) with an image (grandchild) and send it.
Change the parent, child, and grandchild models to suit your situation.
-Routing settings ・ Carrier wave settings required for image posting ・ Creating a table for each model
Let's take a look!
user.rb
class User < ApplicationRecord
  has_many :messages
end
message.rb
class Message < ApplicationRecord
  has_many :pictures
  belongs_to :user
  #The validation of the form written here moves to the form object.
end
picture.rb
class Picture < ApplicationRecord
  belongs_to :message
  #Those who do not post images do not need the following description.
  mount_uploader :picture, PictureUploader
end
There is no description about validation. It's refreshing and easy to read: relaxed:
I would like you to pay attention to only the following two points.
-Update the information of @ message based on the value passed from the parameter in assigns_attributes.
-MessageForm is a class for form objects, which will be explained later.
messages_controller.rb
  #New creation screen
  def new
    @message = MessageForm.new
  end
  #Create New
  def create
    @message = MessageForm.new
    @message.assign_attributes(message_form_params)
    if @message.save
      #Success / failure processing
    end
  end
  #Editing screen
  def edit
    # params[:id]For the part, enter a value that can identify the object to be edited.
    @message = MessageForm.new(message = Message.find(params[:id]))
  end
  #Edit
  def update
    @message = MessageForm.new(message = Message.find(params[:id]))
    @message.assign_attributes(message_form_params)
    if @message.save
      #Success / failure processing
    end
  end
  private
    #Strong parameters
    def message_form_params
      params.require(:message_form).permit(:body, pictures_attributes: [:picture]).merge(user_id: current_user)
    end
The strong parameter pictures_attributes will be explained next.
This is the new message creation screen.
ruby:new.html.erb
<%= form_with model: @message, url:Path for message composition, local: true do |f| %>
  <%= f.text_area :body %>
  <%= f.fields_for :pictures do |picture| %>
    <%= picture.file_field :picture, multiple: "multiple", name: "message_form[pictures_attributes][][picture]" %>
  <% end %>
  <%= f.submit "Send" %>
<% end %>
The explanation will focus on the parameters.
First of all, suppose you typed "OK" in the text area and sent it. The parameters at this time are as follows. (Please check at the terminal.)
Parameters: {"authenticity_token"=>"Abbreviation", "message_form"=>{"body"=>"OK"}, "button"=>""}
It contains the value you entered in the body column.
Here, it is "message_form " because @ message is generated from the MessageForm class. (This will happen automatically.)
Next, if you want to add image information to this, you want to make it look like the following.
Parameters: {"authenticity_token"=>"Abbreviation", "message_form"=>{Image information, "body"=>"OK"}, "button"=>""}
So, let's use the name attribute of file_field.
name: "message_form[pictures_attributes][][picture]"
If you write like this, the parameters when posting two images are as follows. (The image information is long, so it is omitted.)
 Parameters: {"authenticity_token"=>"Abbreviation", "message_form"=>{"pictures_attributes"=>[{"picture"=>1st image information}, {"picture"=>2nd image information}], "body"=>"OK"}, "button"=>""}
You can see that the image information is included under " message_form ".
Also, " pictures_attributes "=> [{"picture "=> corresponds to the controller's strong parameters.
For now, it looks like you can pass the value to the controller: relaxed:
** By the way, you need to be careful in the case of the edit screen. ** ** The reason is written in this article, so please refer to it if you have time.
This is the most complicated part. I will explain it in four parts, so please read it comfortably: ok_hand:
message_form.rb
class MessageForm
  # part-1
  include ActiveModel::Model
  include Virtus.model
  extend CarrierWave::Mount
  validates :body,   presence: true
  attribute :body, String
  attribute :user_id, Integer
  mount_uploader :picture, PictureUploader
  attr_accessor :pictures
  # part-2
  def initialize(message = Message.new)
    @message = message
    self.attributes = @message.attributes if @message.persisted?
  end
  # part-3
  def assign_attributes(params = {})
    @params = params
    pictures_attributes = params[:pictures_attributes]
    @pictures ||= []
    pictures_attributes&.map do |pictures_attribute|
      picture = Picture.new(pictures_attribute)
      @pictures.push(picture)
    end
    @params.delete(:pictures_attributes)
    @message.assign_attributes(@params) if @message.persisted?
    super(@params)
  end
  # part-4
  def save
    return false if invalid?
    if @message.persisted?
      @message.pictures = pictures if pictures.present?
      @message.save!
    else
      message = Message.new(user_id: user_id,
                            body: body)
      message.pictures = pictures if pictures.present?
      message.save!
    end
  end
end
python
  include ActiveModel::Model
  include Virtus.model
  extend CarrierWave::Mount
  validates :body, presence: true
  attribute :body, String
  attribute :user_id, Integer
  mount_uploader :picture, PictureUploader
  attr_accessor :pictures
This is a collection of included modules.
-ActiveModel :: Model allows validation to be used when saving a MessageForm object. The validates part. It's convenient to be able to validate without inheriting ActiveRecord.
-Virtus.model defines the attribute name and type of this class. The attribute part.
It specifies what to receive as a parameter. By the way, I have installed the gem virtus.
-CarrierWave :: Mount is for uploading images. The part of mount_uploader.
attr_accessor is for setting image information in the message. (Used in part-4.)
Give the object a property.
python
  def initialize(message = Message.new)
    @message = message
    self.attributes = @message.attributes if @message.persisted?
  end
The point here is the argument of initialize.
If the controller's new method has an argument, it is receiving it.
python
  def edit
    @message = MessageForm.new(message = Message.find(params[:id]))
  end
In other words, when the object is created with new, if there is information of the message already created, the attributes of the form object are rewritten based on it.
('self.attributes` part)
I'm sorry for those who are getting tired. A little more patience. .. ..
python
  def assign_attributes(params = {})
    @params = params
    pictures_attributes = params[:pictures_attributes]
    #If you write pictures, you can call up the image information.
    @pictures ||= []
    pictures_attributes&.map do |pictures_attribute|
      picture = Picture.new(pictures_attribute)
      @pictures.push(picture)
    end
    #Remove image information from parameters
    @params.delete(:pictures_attributes)
    #Update the information of the message you have already created
    @message.assign_attributes(@params) if @message.persisted?
    #Update form object information
    super(@params)
  end
Here, assign_attributes sets the parameter value to @ message.
The assign_attributes method is originally a method for updating data,
-I want to reflect the editing information of the form object in the already created message.
・ I want to be able to get or set image information by writing pictures (attr_accessor in part-2)
I've modified it a bit for that reason: sunglasses:
Finally the last! It is a conversion work to the Message model.
python
  def save
    #Validate before storage
    return false if invalid?
    #Processing when editing
    if @message.persisted?
      @message.pictures = pictures if pictures.present?
      @message.save!
    else
    #Processing in case of new creation
      message = Message.new(user_id: user_id,
                            body: body)
      message.pictures = pictures if pictures.present?
      message.save!
    end
  end
In the case of editing, it is no different from normal saving.
The @ message at this time is the same as the message in part-2.
The important thing is to convert to the Message object below.
python
      message = Message.new(user_id: user_id,
                            body: body)
I'm passing the value that the form object brought to the column.
This completes the conversion from the MessageForm to the Message object.
python
      message.pictures = pictures if pictures.present?
Finally, set the image information in the message and finish. Thank you for your hard work: clap:
ruby: 2.7.1 rails: 6.0.3.3