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
Therefore, in this implementation, we made this DB structure.
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)
Then, I will implement it immediately. The execution environment is as follows.
Rails 5.2.3
Ruby 2.6.0
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.
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.)
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:
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
.
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>]
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 ^^
I also implemented the update
method in this form, so I will introduce it.
Story of implementing update function in form object
Recommended Posts