(最後更新: 2016-04-27)
fields_for 一對一, 一對多
籍由 user 去更新 profile(1對1), user_languages(1對多) 欄位
user.rb
has_one :profile
has_many :user_languages
accepts_nested_attributes_for :profile
accepts_nested_attributes_for :user_languages
view
<%= form_for @user, url: user_path(@user), html: {method: :put} do |f| %>
<%= f.fields_for :profile do |s| %>
<%= s.text_field :about_me %>
<% end %>
<%= f.fields_for :user_languages do |l| %>
<%= l.check_box :has_badge %>
<%= l.object.from %>
<%= l.object.to %>
<% end %>
<% end %>
產生的 HTML Name :
user[profile_attributes][:about_me]
user[user_languages_attributes][0][has_badge]
controller
user = User.find(params[:id])
user.update(admin_update_params)
def admin_update_params
params[:user].permit(profile_attributes: [:id, :about_me], user_languages_attributes: [:id, :has_badge])
end
它就會更新一對一或一對多了
接下來看一下它到底是怎麼更新的
params[:user] 參數 :
profile_attributes: {
about_me: "Hello world!",
id: "66"
},
user_languages_attributes: {
0: {
has_badge: "1",
id: "143"
}
}
原來 params 會帶該 record 的 id, 所以上面 controller 取參數時要記得 permit :id
Rails 會依照 User model 的關聯下對應的 sql, 注意的是它會先用 user_id + IN (..上面的id參數..), 所以可以確定是這名 User 的資料才可以被 Update
Profile Load (0.3ms) SELECT "profiles".* FROM "profiles" WHERE "profiles"."user_id" = ? LIMIT 1 [["user_id", 69]]
UserLanguage Load (0.3ms) SELECT "user_languages".* FROM "user_languages" WHERE "user_languages"."user_id" = ? AND "user_languages"."id" IN (143, 141, 142) [["user_id", 69]]
SQL (2.8ms) UPDATE "profiles" SET "about_me" = ?, "updated_at" = ? WHERE "profiles"."id" = ? [["about_me", "Hello world!"], ["updated_at", "2015-08-18 09:01:20.299243"], ["id", 66]]
SQL (0.6ms) UPDATE "user_languages" SET "has_badge" = ?, "updated_at" = ? WHERE "user_languages"."id" = ? [["has_badge", "f"], ["updated_at", "2015-08-18 09:01:20.305686"], ["id", 143]]
測試它是否真的安全, 將 fields_for 產生的 HTML 隱藏欄位的值改為一個不屬於此 User 的 id
<input type="hidden" value="143" name="user[user_languages_attributes][0][id]" id="user_user_languages_attributes_0_id">
改成
<input type="hidden" value="123" name="user[user_languages_attributes][0][id]" id="user_user_languages_attributes_0_id">
送出後, 可以看到 Rails 噴錯了:D , 符合預期結果
Couldn't find UserLanguage with ID=123 for User with ID=69
Partial
Pass a variable into a partial
如果是 instance variable (ex: @user
), 不需要特別傳入到 partial, 如果是一般變數才需要 (ex: user
)
<%= form_for @user, url: users_path(@user) do |f| %>
<%= render 'partial file', f: f %>
<% end %>
partial file :
<%= f.text_field :name %>
注意!! 判斷傳入 partial 的變數, 要用 local_assigns
去判斷, 如果直接用
if name <= 會噴錯 undefined local variable or method `name`
OK :
if local_assigns.has_key? :name
if local_assigns[:name]
代入 template 取得 html
<% @progress_bar = render :partial => 'template/progress_bar', :locals => { :num => num, :part_num => part_num, :progress_bar_status => "progress-bar-danger" } %>
<%= render :partial => 'template/url_item', :locals => { :num => num, :progress_bar => @progress_bar } %>
注意檔名要底線 _progress_bar.html.erb
, _url_item.html.erb
layout Template
views/layouts/application.html.erb :
<!DOCTYPE html>
<html>
<head>
<title>Template</title>
<meta charset="utf-8">
<%= favicon_link_tag '/favicon.ico' %> # 要注意一定要 / 因為是從根目錄開始讀, 否則到其他 show 的頁面會把它當 id 在 load
<%= stylesheet_link_tag @controller_name, media: 'all', 'data-turbolinks-track' => true %>
<%= csrf_meta_tags %>
</head>
<body id="page-top" class="index">
<%= render 'layouts/header' %>
<div class="content">
<%= yield %>
</div>
<%= render 'layouts/footer' %>
<%= javascript_include_tag @controller_name, 'data-turbolinks-track' => true %>
</body>
</html>
- layouts/_header.html.erb, _footer.html.erb
- yield 是替換的內容的, ex: users/edit.html.erb
不同頁面引入相同 partial, path 如何根據不同 route 顯示
很簡單, ex:
link_to '評價', request.path
引入個別 view 定義的 CSS / JS
像有些共用的 js 你可以寫在 appication.js,但有些只有這一頁才會用到的 js 可以直接寫在該 view,這樣開發及維護都很方便
layout/application.html.erb
<%= yield :js %>
在個別 view 寫 javascript
<% content_for :js do %>
<script>
$(document).ready(function () {
alert(1);
})
</script>
<% end %>
讓表單送出錯誤後返回資料不清除
controller
def update
...(省略)...
if 驗證錯誤
render :edit
end
end
edit.html.erb
<input type="text" class="form-control" name="xxx" value="<%= params[:xxx] %>">
不要用 redirect_to action: :edit
, 因為 post 的資料不會被帶到 edit 頁面, 它相當於是一個"重新"的 request 請求。 用 render
直接在 update 生出 edit 頁面, post 資料也會被保存下來
或讓 rails 自動幫你綁定回填
<%= f.text_field :nickname, class: 'form-control' %>
但它有個問題是, 一定要執行到 model 裡, 例如 @post.update(params[:post])
最後再 redner :edit
, 如果還沒執行到 model 就 render
那麼資料就不會回填
刪除 validate 錯誤產生多的 div wrapper
正常 :
<%= f.text_field :present_price %>
錯誤時 :
<div class="field_with_errors"><input type="text" value="" name="product[present_price]" id="product_present_price"></div>
解決方法 : 拿掉它, 在 config/environment.rb
最後面加上
ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
html_tag.html_safe
end
JSONP (jbuilder - 預設包在 Rails 的 Gemfile 裡)
ajax 送出請求給 show, show 會去返回 show.js.erb 的 js code 回去給 browser 執行
ajax 以 bootstrap modal 顯示 show action
index.html.erb
<div class="modal fade" id="myModal"
..略..
</div>
link_to '訂單明細', order_path(o), remote: true, data: {toggle: 'modal', target: '#myModal'}
show.js.erb
$(".modal-title").html("訂單明細");
// 填入 html, escape_javascript 可簡寫成 j
$(".modal-body").html("<%= escape_javascript render(partial: 'orders/show')%>");
_show.html.erb
html + ruby code
view
form_for 加上 remote: true
controller
respond_to do |format|
format.js
end
create.js.erb
alert('Success');
JSON 檔
index.json.jbuilder
json.array!(@posts) do |post|
json.extract! post, :id, :title, :content
json.url post_url(post, format: :json)
end
<% UserDomain.domains.each do |k, v| %>
<div>
<%= radio_button_tag 'user[domain]', v %>
<%= label_tag "user_domain_#{v}", t("domain.#{k}") %>
</div>
<% end %>
radio_button("post", "category", "rails")
radio_button("post", "category", "java")
f.radio_button :gender, 'male', checked: true
collection_radio_buttons(:item, :owner_id, Owner.all, :id, :name)
select
未指定值 : 頁面上及 option value 皆顯示一樣
f.select :gender, ['male', 'female', 'others']
有指定值 : 頁面上顯示 male/female/others; option value 為 1 / 2 / 3
f.select :city, {male: 1, female: 2, others: 3}
取出 name 及 id 欄位,直接放入
f.select :category, Category.pluck(:name, :id) 要注意順序是 :name, :id, 否則 select 下拉顯示的會是 id
f.select :category, Category.map { |s| [s.name, s.id] }
空白值, 給 default 值及 class name
f.select :product_spec_id, @product.product_specs.map { |s| [s.name, s.id] }, { include_blank: ture, selected: params[:spec] }, class: 'form-control'
設定 Default
f.select :parent_id, @parent_categories.unshift(["不選擇", 0])
select_tag
select_tag :category_id, options_from_collection_for_select(Category.all, "id", "name")
<select name="category_id" id="category_id">
<option value="1">Music, Games & Kids</option>
<option value="2">Games, Movies & Baby</option>
</select>
collection_select
User has_many UserLocation, 將 User 住的 Location 每項以 Select 列出來
current_user.user_locations.each do |l|
collection_select('user_locations', '', UserLocation.locations_i18n, :last, :first, { include_blank: false, selected: l.location }, { class: 'location', id: nil } )
end
- 第一個參數 : HTML name 名稱
- 第二個參數 : user_locations (第二個參數), 我將它留空
- 第三個參數 : Hash (key: value)
:last, :first
: 會讓 hash 的 key 顯示在 option 的名字, hash 的 value 則是 option 的值
include_blank
: 一定要設定它, 否則 class, id 不會 work
selected
: 如果去掉, 就是未選擇的 <select>
備註
1) f.collection_select(:category_id, Category.all, :id, :name)
等於
f.select :category_id, Category.all.map{ |c| [c.name, c.id] }
2)
s.select :recommended, options_for_select(Translator.recommendeds)
checkbox
f.check_box :rotting
<%= check_box_tag 'user_domains[]', k, @user_domains.has_key?(k), id: "domain-#{k}" %>
Object :
<div class="field">
<%= f.label "Categories" %><br />
<% for category in Category.all %>
<%= check_box_tag 'user[category_ids][]', category.id, @user.category_ids.include?(category.id), :id => dom_id(category) %>
<%= label_tag dom_id(category), category.name, :class => "check_box_label" %>
<% end %>
</div>
Hash : dom_id 無法用, 它只吃 object
<% UserDomain.domain.each do |k, v| %>
<div>
<%= check_box_tag 'user_domains[]', k, @user_domains.has_key?(k), id: "domain-#{k}" %>
<%= label_tag "domain-#{k}", t("domain.#{k}") %>
</div>
<% end %>
參數:
- 第一個參數 name
- 第二個參數 value
- 第三個參數 是否 checked
collection_check_boxes
models/owner.rb
has_many :items
models/item.rb
belongs_to :owner
view :
collection_check_boxes(:item, :owner_id, Owner.all, :id, :name)
file
一般用法
<%= form_for @user, url: users_path, method: :post, html: { class: 'form-inline' } do |f| %>
<%= f.file_field :name %>
<% end %>
Rename resource
<%= form_for @user, as: 'man', url: users_path, method: :post, html: { class: 'form-inline' } do |f| %>
<%= f.file_field :name %>
<% end %>
<input type="text" name="man[name]" id="man_name">
label + text_field
<%= f.label :nickname %>
<%= f.text_field :nickname %>
# 讓 text input disable
disabled: true
# 重新給值或 format datetime
value: @case.deadline.strftime("%Y-%m-%d")
數字
<%= number_field(:product, :price, in: 1.0..20.0, step: 0.5) %>
text_area
f.text_area :bio
<button>
<%= button_tag(type: 'submit', class: "btn btn-primary") do %>
<i class="icon-ok icon-white"></i> Save
<% end %>
<input type="submit">
f.submit "Submit", :disable_with => 'Submiting...'
submit_tag "Submit", id: "foo-submit", data: { disable_with: "Please wait..." }
<%= form_for @user, url: user_path(@user), html: { method: :put, id: 'edit-user' } do |f| %>
取值
@user.email 或
f.object.email
刪除 hidden 欄位 - utf8
<%= form_for (略), enforce_utf8: false %>
link_to
兩者是一樣意思的
<%= link_to user.contacts.name, contact_path(user.contact) %>
<%= link_to user.contacts.name, user.contact %>
block 寫法
<%= link_to edit_user_registration_path do %>
<span class="glyphicon glyphicon-user" aria-hidden="true"></span>
Edit profile
<% end %>
表單 ajax (使用 Unobtrusive JavaScript - UJS)
<%= link_to 'ajax show', event_path(event), :remote => true %>
form_for @user, :remote => true
ajax 送出加上 remote: true
Ajax Link
link_to 'Del', post_path(post), class: 'btn btn-danger', method: :delete, data: {confirm: 'Are you sure?', disable_with: 'Removing'}
label
f.label :name
tag
<%= label_tag "domain-#{k}", t("domain.#{k}") %>
參數:
- id
- 顯示名稱
div
<% @users.each do |u| %>
...
<%= div_for u do %> <div id="user_<%= u.id %>" class="user">
<%= u.ages %> same as => <%= u.ages %>
<% end %> </div>
...
<% end %>
dom id
dom_id(@user) => user_2
輸出
nil 即顯示預設值
<%= @xxx || 'none' %>
輸出換行字元,Replace \n
為 <br>
<%= h(c.text).gsub("\n", "<br>").html_safe %>
截斷指定長度的字串
truncate(user.about_me, length: 100)
=> Stella appositus odio cilicium. Adopto quia magni textus stips libero vergo enim. Iste delibero c...
脫逸
脫逸
<%= @user.ages %>
不脫逸
<%= raw @user.ages %>
脫逸危險標籤
<%=raw sanitize "<script>alert(1);</script>" %> # alert(1);
(?)脫逸 javascript
escape_javascript()
# 可以縮寫為
j()
數字及日期
數字口語化
number_to_human 1234567890 # 1.2 十億
數字 3 位一撇
number_with_delimiter(8400) # 8,400
貨幣符號
number_to_currency(8400) # NT$ 8,400.00 # 如果 locale 為 en => $8,400.00
# 強制指定 locale 及小數位數
number_to_currency(8400, precision: 0, locale: 'zh-TW') # NT$ 8,400
在 model 裡面用 :
ActiveSupport::NumberHelper::number_to_currency(self.price, unit: '$', precision: 0)
extend ActionView::Helpers::NumberHelper
Percent
number_to_percentage("98") # => 98.000%
number_to_percentage(100, precision: 0) # => 100%
日期
time_ago_in_words current_user.created_at # 大約 6 小時
語意化
名詞單複數
pluralize(3, "user") # 3 users
標題化 - 單字的開頭大寫
"man from the boondocks".titleize # Man From The Boondocks
連接詞 (and)
['one', 'two', 'three'].to_sentence # one, two, 和 three
Debug
It will return a <pre>
tag that renders the object using the YAML format
<%= debug @article %>
Displaying an instance variable, or any other object or method, in YAML format
<%= simple_format @article.to_yaml %>
Displaying object values
<%= [1, 2, 3, 4, 5].inspect %>
簡化判斷 + 迴圈的寫法
View 迴圈
如果要使用 each 前需要先判斷他是否為 nil 或 empty, 否則物件空的可能會噴 Error
此寫法只適合傳回空值是 empty 的, 因為可以將 if 及 each 寫在同一行, 就不需要兩層了(if 一層, each 一層),
但在寫法上也比較麻煩一些, 得先知道他是 empty, 還是 nil
取多筆時, 如果不存在, 返回 empty 的話, 如下 :
> @posts = Post.where(user_id: 3333)
Post Load (0.1ms) SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = ? [["user_id", 3333]]
=> #<ActiveRecord::Relation []>
因為返回的是 empty, 不是 nil 所以它會是 ture, 以下寫法可以將 if
及 each
寫成同一行
<% if @posts.each do |post| %>
<%= post.title %>
<% end.empty? %>
You have no posts.
<% end %>
不顯示錯誤訊息的話 :
<% if @posts.each do |post| %>
<%= post.title %>
<% end.empty?; end %>
對於不顯示錯誤訊息的寫法我覺得 end 那邊有點稍亂
希望能繼續找到更好的寫法
其他
判斷目前所在頁面
current_page?(root_path)