[RAILS] A story about saving an image with carrierwave in a nested form using a form object.

What did you do

Thinking about multiple images associated with a post

Carrying out Rails challenges. I am making an app like instagram, and the conditions are as follows.

--Create posts associated with users --Allows you to post multiple images --Use gem carrier wave

Initially, when I saw this condition, I assumed that I would use How to upload multiple files in carrierwave official (save image information in json format in one column). I wondered if it was an issue, but

(Actually, I've done this implementation in the past, and I also wrote Article like this.)

I quoted it in my past article introduced above, ↓ Since I found this article, I remembered that this method might not be good, so I stopped.

Cut off the sweet temptation of ActiveRecord serialize / store

So what did you do

Therefore, in this implementation, we made this DB structure.

Image from Gyazo

The diagram shows that the user has a post and there are multiple images associated with it: relaxed: And I think it would be easier to use accept_nested_attributes_for. .. .. .. While thinking, I heard that this is also a bad implementation, so I decided to use form_object as a review.

↓ For reference, I will introduce past articles. ▼ I have implemented a form using accept_nested_attributes_for Create child table data at once with fields_for (I also write a test) [Rails] [Rspec]

▼ I had made form_object, but I didn't understand it well. Create a parent-child relationship form with form_object (I also write a test)

Implementation

Then, I will implement it immediately. The execution environment is as follows.

Allows you to post images on CarrierWave

There are many very good articles on how to use Carrierwave, so I won't write much. As far as I can find, this article is the most detailed and easy to understand, so please have a look there.

[Rails] Carrier Wave Tutorial

First, install the following Gem ...

Gemfile


gem 'carrierwave'
gem 'mini_magick' #Gem for resizing

After installation, the uploader generated by rails generate uploder Photo is described as follows.

uploaders/photo_uploader.rb


class PhotoUploader < CarrierWave::Uploader::Base
  #The comments that come by default are deleted. Also, it is a description that leaves only the minimum.
  include CarrierWave::MiniMagick

  storage :file
  
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  process resize_to_fill: [400, 300]
  
  def extension_whitelist
    %w(jpg jpeg gif png)
  end
end

Describe as follows in the model file of Image.

models/image.rb


class Image < ApplicationRecord
  mount_uploader :photo, ImageUploader
  belongs_to :post
end

Now you have the basic settings for saving images on carrierwave.

In addition, although it is a method for resizing resize_to_fill, ① mini_magick is installed, And ② It will not work unless image magic k is installed with brew install image magic k.

(It seems that it is related to mini_magick that made imagemagick work with Ruby.)

Create a form with form object

Next, I would like to be able to post an image when post is posted using form object. First of all, I will make it possible to post one.

view

ruby:views/posts/new.html.slim


= form_with model: @post, url: posts_path, local: true do |f|
  = f.label :photo, 'image'
  = f.file_field :photo
  = f.label :text, 'text'
  = f.text_field :text'
  = f.submit 'Post'

controller

controllers/posts_controller.rb


class PostsController < ApplicationController
  def new
    @post = PostForm.new(user_id: current_user.id)
  end

  def create
    @post = PostForm.new(post_params.merge(user_id: current_user.id))
    if @post.save!
      #What to do when a post is successful
    else
      #What to do if the post is unsuccessful
    end
  end

  private

  def post_params
    params.require(:post_form).permit(:text, :photo)
  end
end

form object

forms/post_form.rb


class PostForm
  include ActiveModel::Model

  def initialize(params)
    super(params)
  end

  attr_accessor :text, :photo, :user_id

  validates :text, presence: true

  def save!
    return false if invalid?
    post = Post.new(text: text, user_id: user_id)
    post.images.build(photo: photo).save!

    post.save! ? true : false
  end
end

For the implementation around here, I have referred to this ↓ very much. (It was a coincidence, but it was an article written by an acquaintance. Thank you ...: sparkles :)

[Rails] Create a Model-independent Form using Form Object

As an aside, in my case, unlike the above article, it worked fine without calling uploader on the form object side. .. .. Why: sweat_smile:

Allows you to post multiple images

Now you can post one file. Next, we will make it possible to post 2 files. Rewrite the view file, controller, and form object as follows.

view First, add multiple: true to the file_field option.

ruby:views/posts/new.html.slim


= form_with model: @post, url: posts_path, local: true do |f|
  = f.label :photoes, 'image'
  = f.file_field :photoes, multiple: true # multiple:Add true
  #Omission

In addition, I changed the : photo of the label and column to: photoes, but this is not mandatory and I have changed it for clarity. (However, this change also affects the controller and form_object codes that I'll introduce later.)

controller In controller, specifyphotos: []for the value to be received so that the array can be received.

controllers/posts_controller.rb


class PostsController < ApplicationController
  #Abbreviation. Same as when one file
  private

  def post_params
    params.require(:post_form).permit(:text, photoes: [])
  end
end

form object In form object, rewrite the value accessible by attr_accessor to : photos,

forms/post_form.rb


class PostForm
  include ActiveModel::Model

  #Omission

  attr_accessor :text, :photoes, :user_id

  #Abbreviation

  def save!
    return false if invalid?
    post = Post.new(text: text, user_id: user_id)

    photoes.each do |photo|
      post.images.build(photo: photo).save!
    end

    post.save! ? true : false
  end
end

I turned the save! method for each of the photoes.

Complete

The record created above is as follows. Two images associated with the post have been created in different files properly: relaxed:

3] pry(main)> post = Post.last
  Post Load (1.0ms)  SELECT  `posts`.* FROM `posts` ORDER BY `posts`.`id` DESC LIMIT 1
=> #<Post:0x00007f9296488228
 id: 14,
 body: "2 File posting test",
 user_id: 1,
 created_at: Thu, 31 Dec 2020 05:57:09 UTC +00:00,
 updated_at: Thu, 31 Dec 2020 05:57:09 UTC +00:00>
[4] pry(main)> post.images
  Image Load (0.7ms)  SELECT `images`.* FROM `images` WHERE `images`.`post_id` = 14
=> [#<Image:0x00007f929a502da8
  id: 14,
  photo: "sample_01.jpg ",
  post_id: 14,
  created_at: Thu, 31 Dec 2020 05:57:09 UTC +00:00,
  updated_at: Thu, 31 Dec 2020 05:57:09 UTC +00:00>,
 #<Image:0x00007f929a502ad8
  id: 15,
  photo: "sample_02.jpg ",
  post_id: 14,
  created_at: Thu, 31 Dec 2020 05:57:09 UTC +00:00,
  updated_at: Thu, 31 Dec 2020 05:57:09 UTC +00:00>]

Impressions etc.

The form object had a lot of trouble when I created it last time, but this time I was able to do it quickly: sparkles: I'm glad that it seems to have grown a little from the previous time ^ ^ Also, by chance, I was helped by an article created by a person who graduated from school at the same time. I thought I should do my best too: relaxed:

I will continue to do my best ^^

PS

I also implemented the update method in this form, so I will introduce it.

Story of implementing update function in form object

Recommended Posts

A story about saving an image with carrierwave in a nested form using a form object.
Duplicate an object using a generic type
Form object ~ An epoch-making guy who interacts with multiple models in one form ~
A story about an arithmetic overflow that you shouldn't encounter in Ruby
How to store data simultaneously in a model associated with a nested form (Rails 6.0.0)
How to create an Excel form using a template file with Spring MVC
A story about the JDK in the Java 11 era
A story that got stuck with an error during migration in docker PHP laravel
A story about an error collating checksum values after npm install with Laravel Homestead
How to implement a circular profile image in Rails using CarrierWave and R Magick
[PHP] A story about outputting PDF with TCPDF + FPDI
A story about trying to get along with Mockito
Quickly implement a singleton with an enum in Java
A story about reducing memory consumption to 1/100 with find_in_batches
A story about developing ROS called rosjava with java
I made an interpreter (compiler?) With about 80 lines in Ruby.
A mistake made when displaying an image using Active Storage
A story about BeanNotOfRequiredTypeException occurring after applying AOP in Spring
Mapping to a class with a value object in How to MyBatis
A confused story about a ternary operator with multiple conditional expressions
A story about using the CoreImage framework to erase stains with Swift and implement a blur erase function