When I uploaded the file uploader function with gem rails, I encountered 3 small problems:
- How to validate file extension on client side and server side?
- How to preview the image right after selecting? I will share my solution in those 2 issues.
Create the form
First, remember that you have installed carrierwave:
1 2 3 | gem 'carrierwave', '1.1.0' gem 'mini_magick', '4.7.0' |
Suppose I have a Article model with 2 title and thumbnail fields as follows:
1 2 3 4 5 | <span class="token keyword">class</span> <span class="token class-name">Article</span> <span class="token operator"><</span> <span class="token constant">ApplicationRecord</span> mount_uploader <span class="token symbol">:thumbnail</span> <span class="token punctuation">,</span> <span class="token constant">ImageUploader</span> validates <span class="token symbol">:title</span> <span class="token punctuation">,</span> presence <span class="token punctuation">:</span> <span class="token keyword">true</span> <span class="token keyword">end</span> |
1 2 3 4 5 6 7 8 9 10 11 | <span class="token keyword">class</span> <span class="token class-name">CreateArticles</span> <span class="token operator"><</span> <span class="token constant">ActiveRecord</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Migration</span> <span class="token punctuation">[</span> <span class="token number">6.0</span> <span class="token punctuation">]</span> <span class="token keyword">def</span> change create_table <span class="token symbol">:articles</span> <span class="token keyword">do</span> <span class="token operator">|</span> t <span class="token operator">|</span> t <span class="token punctuation">.</span> string <span class="token symbol">:title</span> t <span class="token punctuation">.</span> string <span class="token symbol">:thumbnail</span> t <span class="token punctuation">.</span> timestamps <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
Create routes, controllers and views for the form
1 2 3 4 | <span class="token constant">Rails</span> <span class="token punctuation">.</span> application <span class="token punctuation">.</span> routes <span class="token punctuation">.</span> draw <span class="token keyword">do</span> resource <span class="token symbol">:articles</span> <span class="token keyword">end</span> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <span class="token keyword">class</span> <span class="token class-name">ArticlesController</span> <span class="token operator"><</span> <span class="token constant">ApplicationController</span> <span class="token keyword">def</span> <span class="token keyword">new</span> <span class="token variable">@article</span> <span class="token operator">=</span> <span class="token constant">Article</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token class-name">end</span> <span class="token keyword">def</span> create <span class="token variable">@article</span> <span class="token operator">=</span> <span class="token constant">Article</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token class-name">article_params</span> <span class="token keyword">if</span> <span class="token variable">@article</span> <span class="token punctuation">.</span> save redirect_to <span class="token function">article_path</span> <span class="token punctuation">(</span> <span class="token variable">@article</span> <span class="token punctuation">)</span> <span class="token keyword">else</span> render <span class="token symbol">:new</span> <span class="token class-name">end</span> <span class="token keyword">end</span> <span class="token keyword">def</span> show <span class="token variable">@article</span> <span class="token operator">=</span> <span class="token constant">Article</span> <span class="token punctuation">.</span> find parmas <span class="token punctuation">[</span> <span class="token symbol">:id</span> <span class="token punctuation">]</span> <span class="token keyword">end</span> <span class="token keyword">private</span> <span class="token keyword">def</span> article_params params <span class="token punctuation">.</span> <span class="token keyword">require</span> <span class="token punctuation">(</span> <span class="token symbol">:article</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> permit <span class="token symbol">:thumbnail</span> <span class="token punctuation">,</span> <span class="token symbol">:title</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #app/views/articles/new.html.erb <div class="col-md-4" style="padding: 88px 0 0 85px;"> <%= form_for @article do |f|%> <div class="form-group"> <%= f.label :title, "Title: " %> <%= f.text_field :title, class: "form-control" %> </div> <div class="form-group"> <%= f.label :thumbnail, "Select a picture:" %> <%= f.file_field :thumbnail, class: "form-control" %> </div> <%= f.submit "Submit" %> <% end %> </div> |
I will add an initializers to add errors_message under each field in the form:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # Tạo file config/initializers/form_error.rb ActionView::Base.field_error_proc = Proc.new do |html_tag, instance_tag| fragment = Nokogiri::HTML.fragment(html_tag) field = fragment.at('input,select,textarea') model = instance_tag.object error_message = model.errors.full_messages.join(', ') html = if field field['class'] = "#{field['class']} invalid" html = <<-HTML #{fragment.to_s} <p class="error">#{error_message}</p> HTML html else html_tag end html.html_safe end |
And we get a form as follows:
Validate file extension
Now, to make a validate extension of the file, I will share two ways:
- Validate client side: Using javascript.
- Validate model level: Use validate in carrierwave’s uploader class
To validate on the client side, we catch the onchange event in file_field as follows:
1 2 | <%= f.file_field :thumbnail, class: "form-control", onchange: "validateFiles(this);" %> |
And add 1 javascript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <span class="token keyword">function</span> <span class="token function">validateFiles</span> <span class="token punctuation">(</span> inputFile <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> extErrorMessage <span class="token operator">=</span> <span class="token string">"File bạn muốn tải lên không đúng định dạng!"</span> <span class="token punctuation">;</span> <span class="token keyword">var</span> allowedExtension <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token string">"jpg"</span> <span class="token punctuation">,</span> <span class="token string">"jpeg"</span> <span class="token punctuation">,</span> <span class="token string">"png"</span> <span class="token punctuation">,</span> <span class="token string">"gif"</span> <span class="token punctuation">]</span> <span class="token punctuation">;</span> <span class="token keyword">var</span> extName <span class="token punctuation">;</span> <span class="token keyword">var</span> extError <span class="token operator">=</span> <span class="token boolean">false</span> <span class="token punctuation">;</span> $ <span class="token punctuation">.</span> <span class="token function">each</span> <span class="token punctuation">(</span> inputFile <span class="token punctuation">.</span> files <span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> extName <span class="token operator">=</span> <span class="token keyword">this</span> <span class="token punctuation">.</span> name <span class="token punctuation">.</span> <span class="token function">split</span> <span class="token punctuation">(</span> <span class="token string">'.'</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">pop</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> $ <span class="token punctuation">.</span> <span class="token function">inArray</span> <span class="token punctuation">(</span> extName <span class="token punctuation">,</span> allowedExtension <span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token operator">-</span> <span class="token number">1</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> extError <span class="token operator">=</span> <span class="token boolean">true</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> extError <span class="token punctuation">)</span> <span class="token punctuation">{</span> window <span class="token punctuation">.</span> <span class="token function">alert</span> <span class="token punctuation">(</span> extErrorMessage <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token function">$</span> <span class="token punctuation">(</span> inputFile <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">val</span> <span class="token punctuation">(</span> <span class="token string">''</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
And it runs as follows:
But with the aforementioned way, when the browser turns off js, it becomes useless. Therefore, I usually perform validate at model level. Carrierwave provides you with the extension_whitelist
method to validate the extension in the class CarrierWave::Uploader::Base
.
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="token keyword">class</span> <span class="token class-name">ImageUploader</span> <span class="token operator"><</span> <span class="token constant">CarrierWave</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Uploader</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Base</span> storage <span class="token symbol">:file</span> <span class="token keyword">def</span> store_dir <span class="token string">"uploads/ <span class="token interpolation"><span class="token delimiter tag">#{</span> model <span class="token punctuation">.</span> <span class="token keyword">class</span> <span class="token punctuation">.</span> to_s <span class="token punctuation">.</span> underscore <span class="token delimiter tag">}</span></span> / <span class="token interpolation"><span class="token delimiter tag">#{</span> mounted_as <span class="token delimiter tag">}</span></span> / <span class="token interpolation"><span class="token delimiter tag">#{</span> model <span class="token punctuation">.</span> id <span class="token delimiter tag">}</span></span> "</span> <span class="token keyword">end</span> <span class="token keyword">def</span> extension_whitelist <span class="token string">%w(jpg jpeg gif png)</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
Add I18n to the message:
1 2 3 4 5 | en: errors: messages: extension_whitelist_error: "Your file was wrong extensions." |
And the validation part will run like this:
Preview the image file immediately after selecting it
To preview the image right after selecting the file (without uploading it to the server), first I will change the select file button a bit:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <span class="token comment">#new.html.erb</span> <span class="token operator"><</span> div <span class="token keyword">class</span> <span class="token operator">=</span> <span class="token string">"col-md-4"</span> style <span class="token operator">=</span> <span class="token string">"padding: 88px 0 0 85px;"</span> <span class="token operator">></span> <span class="token operator"><</span> <span class="token string">%= form_for @article do |f|%> <div class=</span> <span class="token string">"form-group"</span> <span class="token operator">></span> <span class="token operator"><</span> <span class="token string">%= f.label :title, "Title: " %> <%=</span> f <span class="token punctuation">.</span> text_field <span class="token symbol">:title</span> <span class="token punctuation">,</span> <span class="token keyword">class</span> <span class="token punctuation">:</span> <span class="token string">"form-control"</span> <span class="token string">%> </div></span> <span class="token operator"><</span> div <span class="token keyword">class</span> <span class="token operator">=</span> <span class="token string">"form-group"</span> <span class="token operator">></span> <span class="token operator"><</span> p <span class="token operator">></span> <span class="token constant">Select</span> a picture <span class="token punctuation">:</span> <span class="token operator"><</span> <span class="token operator">/</span> p <span class="token operator">></span> <span class="token operator"><</span> label id <span class="token operator">=</span> <span class="token string">"image-label"</span> <span class="token keyword">class</span> <span class="token operator">=</span> <span class="token string">"image-hover"</span> <span class="token keyword">for</span> <span class="token operator">=</span> <span class="token string">"article_thumbnail"</span> <span class="token operator">></span> <span class="token operator"><</span> <span class="token string">%= image_tag "default.png", id: "thumbnail-img", size: "200x200" %> </label> <%=</span> f <span class="token punctuation">.</span> file_field <span class="token symbol">:thumbnail</span> <span class="token punctuation">,</span> <span class="token keyword">class</span> <span class="token punctuation">:</span> <span class="token string">"form-control none"</span> <span class="token string">%> </div></span> <span class="token operator"><</span> <span class="token operator">%</span> <span class="token operator">=</span> f <span class="token punctuation">.</span> submit <span class="token string">"Submit"</span> <span class="token string">%> <% end %></span> <span class="token operator"><</span> <span class="token operator">/</span> div <span class="token operator">></span> |
The image “default.png” you download here and put into assets / images. Add a little css:
1 2 3 4 5 6 7 8 9 10 11 12 | .none { display: none; } .image-hover{ &:hover { opacity: 0.5; transition: all 0.3s ease; } cursor: pointer; } |
And we get a field that selects the image as follows:
We use the FileReader class of js to perform the image preview function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | $(document).on('ready', function() { handle_preview($("#article_thumbnail"), $("#thumbnail-img")); }); function readURL(input, image) { if (input.files && input.files[0]) { var reader = new FileReader(); reader.onload = function(e) { image.attr('src', e.target.result); } reader.readAsDataURL(input.files[0]); } } function handle_preview(input_tag, image){ input_tag.change(function(e){ var file = e.target.files[0]; readURL(e.target, image); }); } |
And the image preview function has completed:
I wrote the handleprevew(input_tag, image)
function handleprevew(input_tag, image)
in the easiest way to reuse. You just need to get the id of the input[type="file"]
tag input[type="file"]
and the id of its label image.
My article here is the end.
Refernces:
https://github.com/carrierwaveuploader/carrierwave/wiki/CarrierWave-and-multiple-databases https://github.com/carrierwaveuploader/carrierwave/wiki/CarrierWave-and-multiple-databases