Introduction
Rails 原生表單
# method 預設是 post
<%= form_for @post, url: posts_path(@post) do |f| %>
<%= f.label :title %>
<%= f.text_field :title %>
<%= f.label :content %>
<%= f.text_field :content %>
<%= f.button :submit, disable_with: 'Submiting' %>
<% end %>
使用 form_for 的話一定要在 controller 的先 new 好 (@post = Post.new
)
所以要 create 必須先 new 好
view :
form_for @post, do |p|
p.text_field :title
end
title 必須與 db 一致, 因為它會去抓 model 的欄位
使用 form_tag 的話欄位名稱就可以自己取, 只不過要自己手動抓欄位名稱 (aaa = params[:title]
)
顯示 validation 錯誤訊息
顯示在欄位後面
<%= f.label :title %> : <%= f.text_field :title, :placeholder => 'At least 5 characters' %><%= @post.errors.full_messages_for(:title).first %>
全部錯誤訊息
<% @post.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
全部錯誤訊息的第一個
<%= @post.errors.full_messages.first if @post.errors.any? %>
顯示某個欄位的錯誤
<%= @post.errors.full_messages_for(:title).first %>
因為 label 及 text_field 會被 <div class="field_with_errors">
包起來, 所以造成跑版
在 config/application.rb 加上就會顯示原始的 html 了
config.action_view.field_error_proc = Proc.new { |html_tag, instance|
html_tag
}
ActionController::InvalidAuthenticityToken
如果是自己寫 HTML 的 form 送出表單造成沒有一起把 token 送出去, 加入以下這行到 form 即可解決
<%= tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token) %>
Install
gem 'simple_form'
執行
rails generate simple_form:install
基本用法
<%= simple_form_for @user, defaults: { input_html: { class: 'default_class' } } do |f| %>
<%= f.input :username, input_html: { class: 'special' }, wrapper_html: { class: 'username' } %> # wrapper_html 會在 label 及 input 外層包一個 div
<%= f.input :password, input_html: { maxlength: 20 }, label_html: { class: 'my_class' } %>
<%= f.input :role, as: :radio_buttons, collection: { t('.role_client') => 'client', t('.role_translator') => 'translator' }, checked: 'client' %>
<%= f.input :remember_me, input_html: { value: '1' } %>
<%= f.button :submit %>
<% end %>
f.input 包含了 label, input
bootstrap
執行
$ rails generate simple_form:install --bootstrap
identical config/initializers/simple_form.rb
create config/initializers/simple_form_bootstrap.rb
exist config/locales
identical config/locales/simple_form.en.yml
identical lib/templates/erb/scaffold/_form.html.erb
locales 只會產生出 simple_form EN 的, 自己再 copy 改成 zh-TW 版本的
基本用法
<div class="row">
<div class="col-md-6">
<div class="panel panel-primary">
<div class="panel-heading">Simple Form: Basic Form</div>
<div class="panel-body">
<%= simple_form_for @post, url: posts_path(@post), html: { class: 'form-horizontal' } do |f| %>
<%= f.error_notification %>
<%= f.input :title, input_html: {class: 'form-control'} %>
<%= f.input :content, input_html: {class: 'form-control'} %>
<%= f.button :submit, disable_with: 'Submiting', input_html: {class: 'btn btn-success'} %>
<% end %>
</div>
</div>
</div>
</div>
連錯誤訊息都會自動顯示在欄位下, 非常方便
submit button 不能用 f.submit
, 應該用 :
`<%= f.button :submit, t('form.submit'), class: 'btn btn-success' %>`
default value, fail validation 會填原本送出的值
<%= f.input :contact_email, required: true, input_html: {value: (params[:user].nil?) ? f.object.email : params[:user]['contact_email']} %>
i18n
copy config/locales/simple_form.en.yml
to config/locales/simple_form.zh-TW.yml
, 替換第一行 en
-> zh-TW
collection + i18n:
Radio
<%= f.input :sex, as: :radio_buttons, collection: [:male, :female] %>
<%= f.input :sex, as: :radio_buttons, collection: User.genders.keys.map { |x| x.to_sym } %>
collection 不能直接用 User.genders.keys, 因為它只是 array, 值一定要 symbol i18n 才會 work
simple_form.zh-TW.yml :
simple_form:
options:
user:
gender:
male: '男'
female: '女'
labels:
user:
gender: "性別"
hints:
user:
gender: "請選擇性別"
Radio + ActiveRecord enum
<%= f.input :gender, as: :radio_buttons, collection: User.genders.keys %>
<div class="row">
<div class="col-md-4 col-md-offset-4">
<h2><%= t('.sign_up', :default => "Sign up") %></h2>
<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= f.error_notification %>
<%= f.input :email, required: true, autofocus: true %>
<%= f.input :password, required: true, hint: (t('.characters_minimum', num: 8) if @minimum_password_length) %>
<%= f.input :password_confirmation, required: true %>
<%= f.button :submit, t('form.submit'), class: 'btn btn-success' %>
<% end %>
<%= render "devise/shared/links" %>
</div>
</div>
gem 'bootstrap_form'
application.css 加上
/*
*= require rails_bootstrap_forms
*/
基本用法
<%= bootstrap_form_for(@user) do |f| %>
<%= f.email_field :email %>
<%= f.password_field :password %>
<%= f.check_box :remember_me %>
<%= f.submit "Log In" %>
<% end %>
它會產生
<form accept-charset="UTF-8" action="/users" class="new_user" id="new_user" method="post">
<div class="form-group">
<label for="user_email">Email</label>
<input class="form-control" id="user_email" name="user[email]" type="email">
</div>
...略...
</form>
Paperclip
paperclip, 上傳檔案或處理圖片, 對 avatar 可以處理的很乾淨
安裝 paperclip
gem "paperclip", "~> 4.3"
安裝 imagemagick
Mac
brew install imagemagick
Ubuntu
sudo apt-get install imagemagick -y
設定 ImageMagick utilities 的指令路徑
可用以下方法確認你的 convert
指令找的到, 有的話可以先不用設定
$ which convert
/usr/local/bin/convert
如果不能正常運作, 再設定 config/environments/development.rb
:
Paperclip.options[:command_path] = "/usr/local/bin/"
上傳 avatar 圖片
1) models/user.rb
has_attached_file :avatar,
styles: { medium: "300x300>", thumb: "100x100>" },
# default_url: "/images/:style/missing.png",
default_url: ->(attachment) { ActionController::Base.helpers.image_path('icons/avatar.png') }, # 在 production 才會正確顯示出來
url: "/:class/:attachment/:id/:style.:extension",
path: ":rails_root/public:url"
validates_attachment :avatar, presence: true,
content_type: { content_type: /\Aimage\/.*\Z/ }, # 或特定類型 content_type: "image/jpeg" or content_type: ['image/jpeg', 'image/png', 'image/gif']
size: { in: 0..20.megabyte } # 或 KB { in: 0..20.kilobytes }
validates_attachment_content_type :avatar, :content_type => /\Aimage\/.*\Z/
- 儲存路徑是 :
/public/system/users/avatar/000/000/001/original/xxxx.jpg
- 要注意 nginx 的上傳檔案大小
client_max_body_size 50M;
2) 新增 avatar 所需的 DB 欄位
rails generate paperclip user avatar
產生 db/migrate/20150713155732_add_attachment_avatar_to_users.rb
, 內容為
class AddAttachmentAvatarToUsers < ActiveRecord::Migration
def self.up
change_table :users do |t|
t.attachment :avatar
end
end
def self.down
remove_attachment :users, :avatar
end
end
執行 rake db:migrate
它會在你的 users 加上這些欄位
t.string "avatar_file_name"
t.string "avatar_content_type"
t.integer "avatar_file_size"
t.datetime "avatar_updated_at"
如果只是要增加欄位
add_attachment :users, :avatar
3) 加上 avatar 相關程式
views/dashboard/welcome/index.html.erb
<%= form_for @user, :url => users_path, :html => { :multipart => true } do |form| %>
<%= form.file_field :avatar %>
<% end %>
或 simple_form 版
<%= simple_form_for @user, url: dashboard_welcome_update_avatar_path do |form| %>
<%= form.input :avatar, as: :file %>
<%= form.submit '上傳' %>
<% end %>
顯示原圖, medium and thumb 大小圖片
<%= image_tag @user.avatar.url %>
<%= image_tag @user.avatar.url(:medium) %>
<%= image_tag @user.avatar.url(:thumb) %>
controllers/dashboard/welcome_controller.rb
class Dashboard::WelcomeController < ApplicationController
def index
@user = current_user
end
def update_avatar
if User.update(current_user, avatar_params)
redirect_to action: :index
end
end
private
def avatar_params
params.require(:user).permit(:avatar)
end
end
config/routes.rb
namespace :dashboard do
root 'welcome#index'
patch 'welcome/update_avatar'
end
參數說明
:url
: host 後面那段 url 路徑,也是檔案相對路徑
:path
: 檔案儲存位置的完整路徑
:default_url
: 如果找不到圖片時的預設圖
:styles
: A hash of thumbnail styles with geometries. If you need copies of uploaded files with particular dimensions then specify them here.
hash
has_attached_file :avatar, {
:url => "/system/:hash.:extension",
:hash_secret => "longSecretString"
}
image
has_attached_file :avatar, :styles => {:thumb => 'x100', :croppable => '600x600>', :big => '1000x1000>'}
has_attached_file :cover, :styles => {:small => 'x100', :large => '1000x1000>'}
has_attached_file :sample, :styles => {:thumb => 'x100'}
Dynamic Processor
has_attached_file :avatar, :styles => lambda { |attachment| { :thumb => (attachment.instance.boss? ? "300x300>" : "100x100>") } }
has_attached_file :avatar, :processors => lambda { |instance| instance.processors }
attr_accessor :processors
不檢查檔案格式一定要加上
do_not_validate_attachment_file_type :client_uploading
尺寸的符號
:styles => { :medium => "300x300>", :thumb => "100x100>" }
>
: 等比例, 將圖片的大小縮到小於這尺寸, 較常用
<
: 等比例, 將圖片的大小縮到大於這尺寸
#
: 等比例, 設定的最長邊與圖片的最長邊相接, 裁切多餘部分, 一般用於縮圖或頭像
!
: 非等比, 強制圖片長寬和該尺寸一樣大
^
: 等比例, 圖片的大小最小要那麼大
無
: 等比例, 圖片的大小最大要那麼大
儲存路徑參數
:style
: original
:basename
: 檔名
:id
: TABLE primary key
:id_partition
:
:fingerprint
:attachment
: 欄位名稱 (複數), ex: avatars
:extension
: .jpeg
:class
: model name, ex: users
不同大小的 avatar
url: "/:class/:id/avatar/:style.:extension",
path: ":rails_root/public:url",
/users/4/avatars/original.jpeg?1436890118
/users/4/avatars/medium.jpeg?1436890118
/users/4/avatars/thumb.jpeg?1436890118
檔案不公開, 在根目錄建立 private (與 public 同層)
url: "/:class/:id/:basename.:extension",
path: ":rails_root/private:url"
/users/3/profile.png
Custom path
url: "/:class/:uuid/avatar/:style.:extension"
Paperclip.interpolates :uuid do |attachment, style|
attachment.instance.uuid # uuid 是欄位名稱
end
Default url
預設
default_url: "/images/:style/missing.png",
如果要使用 assets/images 下的圖片,必須這樣設置,在 production 才會正確顯示出來
default_url: ->(attachment) { ActionController::Base.helpers.image_path('icons/avatar.png') },
其他
刪除檔案
u.avatar = nil
u.save(validate: false)
Private 檔案下載
if @case && @case.original_file && File.exist?(@case.original_file.path)
send_file @case.original_file.path
end
判斷是否已上傳檔案
@users.avatar.exists?
必須上傳圖片
validates :photo, presence: true
Paperclip + Crop
簡易 crop
加上 convert_options
has_attached_file :avatar,
styles: { medium: "300x300>", thumb: "100x100>" },
url: "/:class/:id/avatar/:style.:extension",
path: ":rails_root/public:url",
convert_options: {
#the gravity parameter takes "Center" and directions like "northeast, nort, west"
:thumb => "-gravity northwest -crop 100x100+0+0 +repage", #crop to 100x100 starting at upper left corner (northwest)
:medium => "-gravity northwest -crop 101x100+100+0 +repage", #crop to 100x100 starting 100 pixels to the right of the upper left corner
}
使用 papercrop
Gemfile
gem 'papercrop', '~> 0.3.0'
application.js
//= require jquery.jcrop
//= require papercrop
application.scss
*= require jquery.jcrop
controller
def upload_avatar
current_user.update(avatar_params)
redirect_to action: :edit
end
def crop_avatar
current_user.update(crop_params)
redirect_to action: :edit
end
def avatar_params
params[:user].permit(:avatar)
end
def crop_params
params[:user].permit(:avatar_original_w, :avatar_original_h, :avatar_aspect, :avatar_box_w, :avatar_crop_x, :avatar_crop_y, :avatar_crop_w, :avatar_crop_h)
end
model
has_attached_file :avatar,
styles: { medium: "300x300>", thumb: "100x100>" },
url: "/:class/:id/avatar/:style.:extension",
path: ":rails_root/public:url",
processors: [:papercrop] # 加上它
validates_attachment :avatar, content_type: { content_type: ['image/jpeg', 'image/png', 'image/gif'] }, size: { in: 0..10.megabyte }
crop_attached_file :avatar # 加上它
view
上傳的 view
<%= form_for @user, url: upload_avatar_info_user_path(@user), method: :PATCH do |f| %>
<%= image_tag @user.avatar.url %>
<%= image_tag @user.avatar.url(:thumb) %>
<%= image_tag @user.avatar.url(:medium) %>
<%= f.file_field :avatar, as: :file %>
<%= f.submit 'Save' %>
<% end %>
crop 的 view
<%= form_for @user, url: crop_avatar_info_user_path(@user), method: :PATCH do |f| %>
<%= f.cropbox :avatar, width: 500 %>
<%= f.crop_preview :avatar, width: 100 %>
<%= f.submit 'Save' %>
<% end %>
crop 不會修改 original 的圖片只會改 thumb 及 medium 的圖
Paperclip 上傳到 S3
安裝 & 設定
Install aws-sdk
gem "paperclip", "~> 5.0.0.beta2" # 注意! 5 版以上才支援 aws-sdk 2 版
gem 'aws-sdk', '~> 2.2.37'
-
先到 IAM 建立一個有 S3 權限的 User, 並記下 Access Key ID
Secret Access Key
-
到 S3 -> Create Bucket -> 設定 Policy, 設定後簡單使用 command 上傳測試是否 work, 請參考此篇 - AWS-SDK / AWS-CLI 上傳及下載
-
設定給 paperclip 讀取 s3 的 config。可以設定在 config/application.rb
config.paperclip_defaults = {
storage: :s3,
s3_region: ‘ap-northeast-1’,
s3_credentials: {
bucket: ‘my-bucket’,
access_key_id: ‘Access Key ID’,
secret_access_key: ‘Secret Access Key’,
}
}
-
Model 的 Paperclip 設定
s3_host_name: “s3-ap-northeast-1.amazonaws.com”,
url: “/:class/:attachment/:id/:style.:extension”,
path: “:url” # path 跟 url 一樣就好了
-
輸出 @product.photo.url(:thumb)
它就會自動組出正確的 url 了
http://s3-ap-northeast-1.amazonaws.com/my-bucket/products/photos/1/thumb.jpeg?1461739010
-
完成
區分 Development 使用本機空間, Production 使用 S3
-
加上環境判斷, config/application.rb
if Rails.env.production?
config.paperclip_defaults = {
…
}
end
-
(選項)區分 path
如果是 development 上傳到網站根目錄的 /public
path: (Rails.env.production?) ? “:url” : “:rails_root/public:url”
s3_host_name 不需處理它, 即使 development 存在這個參數也無妨
s3_host_name: “s3-ap-northeast-1.amazonaws.com”,
-
完成! 記得要重啟動 Rails