Springok's Blog

Posts match “ gem ” tag:

Rails如何使用Carrierwave上傳圖片

| Comments

這篇介紹如何使用Carrierwave實作圖⽚上傳功能與並以mini_magick產生不同尺寸的數張圖片。

目前carrierwave是上傳圖片時普遍選用的gem,而且有很多功能可以玩,例如可整合另一個自動裁切圖片大小的gem叫mini_magick,下面簡單記錄我實作的一些步驟與設定。

另外要使用mini_magick前,須事先安裝image magick,如果是mac環境底下,應該跑一下下面指令就可以完成安裝了。

$ brew install imagemagick

不知道自己有沒有裝,跑一下

$ convert -version

OK的話就會出現類似下面的畫面:

$ convert -version
Version: ImageMagick 6.8.9-7 Q16 x86_64 2014-09-11 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2014 ImageMagick Studio LLC
Features: DPC Modules
Delegates: bzlib fftw freetype jng jpeg lcms ltdl lzma png tiff xml zlib

情境說明:

User story:

賣方在網站上架商品時,要可以上傳實物照片。

Model:

  • Product
  • Photo

實作步驟:

  1. 安裝Gem
  2. 設定model與建立關係
  3. 在Rails console確認關係
  4. 設定上傳圖片時,同時切成各種尺寸的圖片供使用
  5. 設定.gitignore (選擇性步驟,但建議作)
  6. 修改controller與頁面
  7. 上傳看看吧~

Step1. 安裝Gem

安裝跟其他gem一樣,在Gemfile新增兩行

Gemfile
gem 'carrierwave'
gem 'mini_magick'

再執行

$ bundle install

然後新增一個Uploader,命名為image(舉例說明,可依需求命名)

$ rails generate uploader image

Step2. 設定model與建立關係

產生一個model, photo用來存放照片

$ rails g model photo product_id:integer image:string
# product_id 是foreign_key, image則是之後給mount_uploader的欄位

跑migration

$ rake db:migrate

接著Photo中加入關係與mount_uploader

photo.rb
class Photo < ActiveRecord::Base
  belongs_to :product

  mount_uploader :image, ImageUploader
end

在Product中也做相關宣告

product.rb
class Product < ActiveRecord::Base
  has_many :photos, dependent: :destroy

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

end

Step3. 在Rails console確認關係

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

config/appliaction.rb
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。

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

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

app/uploaders/image_uploader.rb
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

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

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

.gitignore
   ()
+ public/uploads

Step6. 修改Controller與頁面

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

 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
…(略)

  <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
…(略)

  <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>

產生的頁面會長這樣:

Step7. 上傳看看吧~

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

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

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的第一張圖片舉例。

以上簡單整理供參考,有問題也可一起討論~感謝!

參考資料

Railscast的carrierwave教學影片
https://www.youtube.com/watch?v=YpF_4uciMvg
Carrierwave裁切各種不同版本的圖片設定
https://github.com/carrierwaveuploader/carrierwave#adding-versions
Carrierwave如何設定遠端上傳圖片
https://github.com/carrierwaveuploader/carrierwave#uploading-files-from-a-remote-location

ActionMailer與Mailgun實作

| Comments

最近練習專案中有用到ActionMailer,簡單記錄一下使用方式與連接的第三方SMTP寄信服務。

利用E-mail發送各種資訊給網站會員或電子報訂閱者,已經是各網站必備的服務功能,而Rails中的ActionMailer也幫我們做了很好的整合。

一旦我們做好網站寄送Email的相關規劃,如寄送時機、內容等等,就可以利用ActionMailer來自動發信,舉例來說,當消費者在購物網站下單後,寄送訂單確認信,或是用戶註冊後,寄送歡迎信等等。

透過下面幾個步驟就可以讓我們設定好網站寄信的功能:

  1. 建立Mailer
  2. 編輯對應樣版(Template)
  3. 設定寄信者
  4. 在本機預覽Mail與套用CSS格式
  5. 連結第三方SMTP寄信服務(Mailgun)

Step1. 建立Mailer

跟建立Controller很類似,首先我們先建立一個Mailer,而這個Mailer類別(class)中的一個方法(method)也會對應到一個樣板(template),舉例來說,產生一個OrderMailer的指令如下:

$ rails g mailer ordermailer

接著編輯ordermailer,例如新增寄發訂單確認信的方法(method):

app/mailer/order_mailer.rb
def notify_order_place(order)  # 當這個方法被呼叫時,我們希望可以得到order的資訊

     @order = order
     @user = order.user
     @order_items =@order.items
     # 從order裡萃取出等等所需要用的各個實例變數(instance variable)


     mail(to: @user.email, subject: "[ECstore] 感謝您的訂單,以下是您這次的訂單商品明細 #{order.id}")
     # 最後設定收件者與標題並寄送Email

end

Step2. 編輯對應樣版(Template)

app/views/order_mailer/notify_order_placed.html.erb
<div class="row">
  <div class="col-md-12">
    <h2>
      訂單明細 <br>
      <small>
        <%= link_to("訂單連結", order_url(@order.id)) %>
      </small>
    </h2>
    <table class="table table-bordered">
      <thead>
        <tr>
          <th width="70%">商品明細</th>
          <th>單價</th>
          <th>數量</th>
          <th>小記</th>
        </tr>
      </thead>
      <tbody>

        <% @order_items.each do |order_item| %>
          <tr>
            <td>
              <%= order_item.product_name %>
            </td>
            <td>
              <%= order_item.price %>
            </td>
            <td>
              <%= order_item.quantity %>
            </td>
            <td>
              <%= order_item.quantity * order_item.price %>
            </td>
          </tr>
        <% end %>

      </tbody>
    </table>
   </div>
</div>

Step3. 設定寄信者

app/mailer/application_mailer.rb
 default from: servicenoreply@service.com"
 # 可將預設的寄信者設定改掉,以符合使用需求

Step4. 在本機預覽Mail與套用CSS格式

接下來,我們希望能在本機開發端預覽Mail,確認Mail的內容與格式,同時也確認OrderMailer這個Actionmailer運作正常,為了達到預覽目的,需求搭配使用一個Gem叫letter_opener。
首先安裝Gem:

Gemfile
group :development do
     gem "letter_opener"
end

接下來跑bundle install,然後在開發環境的config設定:

config/environments/development.rb
()
config.action_mailer.default_url_options = { host: 'localhost:3000 } #本機端

config.action_mailer.delivery_method = :letter_opener

接下來在Rails c底下輸入:

>> OrderMailer.notify_order_placed(Order.last).deliver!
#抓出最後一筆訂單資料,並寄送訂單確認信

如此一來,瀏覽器應該會就開一個新分頁,顯示發送mail的內容如下:

但可以看出信件格式好像怪怪的,是因為Mailer的樣板並沒有套用CSS的關係,這封mail如先前編輯的是一份HTML格式的文件,如果要讓收信者能看到相關CSS的呈現,勢必也要讓其擁有相關的CSS設定,在這邊可借助Roadie這個Gem達到目的。

首先安裝Gem

Gemfile
group :development do
     gem "letter_opener"
 +   gem roadie"
end

接下來跑bundle install,在樣式底下加入

app/views/order_mailer/notify_order_placed.html.erb
+    <link rel="stylesheet" type="text/css" href="/assets/application.css" />
     # 加入CSS檔案的所在位置連結

<div class="row">
  <div class="col-md-12">
    <h2>
      訂單明細 <br>
      <small>
        <%= link_to("訂單連結", order_url(@order.id)) %>
      </small>
…(略)

再寄送一次試試:

已經套用了基本的表格了。

Step5. 連結第三方SMTP寄信服務(Mailgun)

而寄信方式的選項有:test, :sendmail, :smtp。
其中SMTP (Simple Mail Transport Protocol)又稱"簡單郵件傳輸協議" 是電子郵件傳輸的標準協定,是比較推薦的寄信方式,用Gmail就可以實作,但通常網站服務都需要大量寄信,自架mail server通常不會是個人選項,這時候就可以利用一些第三方的寄信服務,這篇則是以Mailgun說明。

首先,到官網去申請帳號,免費帳號每個月的前一萬封mail免費,註冊完之後,點擊上方Domains進入後,可看到一預設的domain,在點擊後,進入如下的顯示各資訊的畫面,利用這些資訊在Rails中做相關設定。

到Rails環境底下做mail寄送的設定:

config/environments/development
()

config.action_mailer.default_url_options = {  host: 'localhost:3000’ }
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
    address:              'smtp.mailgun.org',
    port:                  587,
    domain:               'sandboxb50419f43d7a4fe590dda7e73ea0896a.mailgun.org',
    user_name:             ENV['SMTP_USERNAME'],
    password:              ENV['SMTP_PW'],
    authentication:       'plain',
    }
()

相關帳密利用(1)環境變數設定或(2)設定在yml檔中,並將該yml檔加入.gitignore,避免帳密外洩。

這樣就設定完成了,把收件者設成自己的Email,然後寄信試試看吧~

PS:在部署環境(Production)下的設定方式也相同,而帳密利用環境變數做設定的,要記得在部署環境中加入相關變數,才讀得到值。

Ref:
ActionMailer
https://ihower.tw/rails4/actionmailer.html
http://guides.rubyonrails.org/action_mailer_basics.html
Letter_Opener
https://github.com/ryanb/letter_opener
Roadie
https://github.com/Mange/roadie
第三方SMTP寄信服務介紹
http://www.yogoeasy.com/mandrill-mailgun-guide/