编程套路:ROR实现product上传多图的步骤

##以商店product添加多图为例
###paperclip

Step 1

1
2
gem 'paperclip'
bundle install

重开rails s

Step 2

修改app/models/product.rb,加入

1
2
3
has_attached_file :image, styles: { medium: "300x300>", thumb: "100x100>" },
default_url: "/images/:style/missing.png" #这一行可不要#
validates_attachment_content_type :image, content_type: /\Aimage\/.*\Z/

Step 3

1
rails generate paperclip product image

确认生成的XXXXXXX_add_attachment_image_to_products.rb如下

1
2
3
4
5
6
7
8
9
10
11
class AddAttachmentImageToProducts < ActiveRecord::Migration
def self.up
change_table :products do |t|
t.attachment :image
end
end

def self.down
remove_attachment :products, :image
end
end
1
rake db:migrate

Step 4

在app/views/admin/product相关页面加入如下

1
2
3
+<div>
+<%= f.input :image, as: :file %>
+</div>

Step 5

修改app/controllers/admin/products_controller如下

1
2
3
4
private
def product_params
params.require(:product).permit(:title, :description, :quantity, :price, :image) #:image加入參數
end

Step 6

图片调用方法

1
2
3
<%= image_tag @user.avatar.url %>
<%= image_tag @user.avatar.url(:medium) %>
<%= image_tag @user.avatar.url(:thumb) %>

carrierwave(我第一次实操的时候上面的paperclip和这个都进行了操作,所以不知道最后是那个起了作用,因而可以分开实验下)

Step 1

1
2
+gem 'carrierwave'
+gem 'mini_magick'
1
bundle install

Step 2

1
2
$ convert -version
$ brew install imagemagick

Step 3

1
2
3
$ rails g uploader image
$ rails g model photo product_id:integer image:string
$ rake db:migrate

Step 4

接著Photo中加入關係與mount_uploader

photo.rb
1
2
3
4
5
class Photo < ActiveRecord::Base
belongs_to :product

mount_uploader :image, ImageUploader
end

在Product中也做相關宣告

product.rb
1
2
3
4
5
6
7
class Product < ActiveRecord::Base
has_many :photos, dependent: :destroy

accepts_nested_attributes_for :photos
#之後我們要做nested form,先在這邊設定接受變更Photo底下的attributes

end

Step 5(这步可不做)

在Rails console確認關係

首先需要修正無法在 rails c 讀取到 uploader.rb 的問題:

config/appliaction.rb
1
2
3
4
5
6
7
module Artstore
class Application < Rails::Application
…(略)
config.active_record.raise_in_transactional_callbacks = true
+ config.autoload_paths += %W(#{config.root}/app/uploaders)
end
end

接下來在Rails c輸入Product.first.photos應該就可以確認結果,如下結果就是成功,但因為目前我們還沒上傳任何圖片,回傳的是[ ],而到目前為止圖片上傳功能的Model端設定已經OK。

Step 6

設定上傳圖片時,同時切成各種尺寸的圖片供使用

然後編輯你的image_uploader.rb 讓MiniMagick可以用還可將照片切成各種尺寸

app/uploaders/image_uploader.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ImageUploader < CarrierWave::Uploader::Base
…(略)

+ include CarrierWave::MiniMagick #設定使用minimagick

…(略)

+ process resize_to_fit: [800, 800] #圖片上傳後,自動切成你要的size


+ version :thumb do #設同時切其他size的版本-thumb

+ process resize_to_fill: [200,200]
+ end

+ version :medium do #設同時切其他size的版本-medium

+ process resize_to_fill: [400,400]
+ end

end

Step 7

設定.gitignore (選擇性步驟,但建議作)

上傳處理後的圖片自動存放在public/uploads底下,而建議將public/uploaders 資料夾放入 .gitignore,因為commit這些上傳圖片的變更,事實上也不太有用。

1
2
3
.gitignore
…(略)
+ public/uploads

Step 8

修改Controller與頁面

完成以上步驟後,接下來就只要在Controller與頁面加入對應的上傳欄位就可以啦~
以我練習的專案來說,是在新增商品的步驟做圖片上傳,所以在Product#new底下,除了原本的@product之外,多宣告一個@photo,並且在product_params的底下允許存取image這個欄位,像這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  def new
@product = Product.new
@photo = @product.photos.new
end

…(略)

private
def product_params
params.require(:product).
permit(:title, :description, :quantity, :price, photos_attributes: [:image])
# 配合先前在Product model中的設定,使用nested_attributes的設定方式,通過驗證。


end

然後在對應的new.html.erb,加入該上傳欄位:

new.html.erb
1
2
3
4
5
6
7
8
9
10
…(略)

<div class="form-group">
<%= f.simple_fields_for :photos do |c| %>
<%= c.input :image, as: :file %>
<% end %>
</div>
# 這邊是用我自己在練習的專案作說明,使用simple_form,
# 然後是在已有的表單底下,增加額外的欄位,所以用的是fields_for指令,
# 而要從本地端電腦上傳圖片,所以是as: :file。

或者你也可以給定圖片所在的遠端URL,來上傳圖片,CarrierWave已經很貼心的幫我們做好整合,也有固定的命名方式,所以只要更改上傳欄位的名稱為 :remote_xxx_url,並在product_params的底下允許存取remote_xxxx_url這個欄位,就OK啦,像這樣:

new.html.erb
1
2
3
4
5
6
7
8
…(略)

<div class="form-group">
<%= f.simple_fields_for :photos do |c| %>
<%= c.input :image, as: :file %> # 使用本地端上傳,手動更改type為File
<%= c.input :remote_image_url%> # 使用遠端url上傳,使用預設type即可(String)
<% end %>
</div>

Step 9

在頁面中選擇其中一種方式上傳後,專案底下public/uploads/photos/image應該就會有三張不同尺寸的圖片囉~

之後如果要在頁面中使用圖片路徑,像下面使用就可以指向圖片所在位置把對應圖片叫出來囉~

1
2
3
4
5
6
7
 Photo.first.image.url        =>"/uploads/photo/image/1/xxx.jpg"        # size: 800x800

Photo.first.image.thumb.url =>"/uploads/photo/image/1/thumb_xxx.jpg" # size: 200x200

Photo.first.image.medium.url =>"/uploads/photo/image/1/medium_xxx.jpg" # size: 400x400

#以Photo的第一張圖片舉例。

附录

我在实现实现上传和显示是最终使用的是如下的语句

app/controller/admin/templates.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def new
@template = Template.new
@templatephoto = @template.templatephotos.build
end
def create
@template = Template.new(template_params)

if @template.save
if params[:templatephotos] != nil
params[:templatephotos]['image'].each do |a|
@templatephoto = @template.templatephotos.create(:image => a)
end
end
redirect_to admin_templates_path
else
render :new
end
end
private

def template_params
params.require(:template).permit(:title, :description, :price, :version, :proportion, templatephoto_attributes: [:image, :id])
end
new.html.erb
1
2
3
4
5
6
...(略)
<div class="group">
<%= f.file_field :image, :multiple => true, name: "templatephotos[image][]" %>("请上传5张图片")<br>
</div>

<%= f.submit "Submit", data: { disable_with: "Submitting..." } %>
edit.html.erb
1
2
3
4
5
6
7
8
9
10
<% if @template.templatephotos.present? %>
<span>目前商品图</span><br>
<% @template.templatephotos.each do |p| %>
<%= image_tag p.image.thumb.url %><br />
<% end %>
<% end %>

<div class="group">
<%= f.file_field :image, :multiple => true, name: "templatephotos[image][]" %>("请上传5张图片")<br>
</div>
作者

Han Wei

发布于

2017-02-25

更新于

2024-04-07

许可协议

评论