form_for相關問題:想要把複選題改成單選題


#1

本來弄了一個複選題(check_box_tag)的問卷,跑得好好的,沒有問題。
可是現在想要改成單選題(radio_button_tag),但是一直試不出來,不知道哪裡出問題了?

> <h1>回答更多題目</h1>

>  


> <%= form_for [@user, @answersheet], url: dashboard_user_answersheet_path, method: :PATCH do |f| %>


>   <p>
>   <%= f.label :title %><%= f.text_field :title %><br/>
>     <div>
>       <table class="table">
>         <thead>
>           <tr>
>           <td>#</td>
>           <td>Questions ID</td>
>           <td>Questions</td>
>           <td>Answers</td>
>           </tr>
>         </thead>
>         <tbody>
>           <%= hidden_field_tag 'answersheet[answer_ids][]','' %>
>           
>             <tr><% @question.each do |question| %> 
>               <td>#</td>
>               <td><%= label_tag question.id %></td>
>               <td><%= label_tag question.content %></td>
>               <% question.answer.each do |answer| %> 
>               <td> 
>               <%= check_box_tag "answersheet[answer_ids][]", answer.id, @answersheet.answer.include?(answer) %>
>               <%= label_tag answer.content %>
>               </td>
>               <% end %>
>             </tr>
>           <% end %>
>         </tbody>
>       </table>
>     </div>
>   </p>

>   <div class="actions">
>   <%= f.submit %>
>   </div>
> <% end %>   

我把:
> <%= check_box_tag "answersheet[answer_ids][]", answer.id, @answersheet.answer.include?(answer) %>

改成:
> <%= radio_button_tag "answersheet[answer_ids][]", answer.id, @answersheet.answer.include?(answer) %>

結果真的變單選題了,十題只可以有一個答案,請問VIEW裡面有寫錯嗎?


#2

source code radio_button_tag(name, value, checked = false, options = {})

你options為什麼傳@answersheet.answer.include?(answer)?

你可能要加上的group去把option包起來

<div class="field"> 
    <% question.answer.each do |answer| %> 
      <%= f.radio_button_tag "answersheet[answer_ids][]", answer.id, false %>
    <% end %>
</div>

你也可以印出html code比較好判斷


#3

在check_box_tag的時候:
因為是edit。所以在new之後(回答問題),如果要edit(改變答案),就必須要驗證之前在new的時候是不是回答過。
所以我以為在radio_button_tag的時候,也是要做同樣的驗證。

這句話的意思是要加上div嗎?
我加了div也改成false,可是還是一樣的結果。


#4

check_box_tag

radio_button_tag


#5

你所有的 radio button 都歸類於 answersheet[answer_ids][]這個group,因為你的name都設定成一樣,所以傳入給server時都會包在同一個group下,因此你每一題應該都屬於單一個group只要把name改掉就可以了

<input type="radio" name="group1" value="1"> 
<input type="radio" name="group1" value="2" > 
<input type="radio" name="group1" value="3">

<input type="radio" name="group2" value="1"> 
<input type="radio" name="group2" value=2"> 
<input type="radio" name="group2" value="3">

radio_button_tag(name, value, checked = false, options = {})
你每個問題的選項name都要改成同一個group


#6

喔喔,了解。多謝幫忙。
不過,改成你的方法後,值(answer_ids)反而送不出去耶。

如果用這個方法,name會不一樣。
> <%#= radio_button_tag "#{question.id}", answer.id, @answersheet.answer.include?(answer) %>

可是會出現這樣,顯示值沒有丟到answer_ids裡面去:“answersheet”=>{“title”=>"", “answer_ids”=>[""]}, “1”=>“1”, “3”=>“6”, “4”=>“9”, “5”=>“12”,

> Started PATCH "/dashboard/users/2/answersheets/1" for ::1 at 2015-11-29 21:04:22 +0800
> Processing by Dashboard::AnswersheetsController#update as HTML
>   Parameters: {"utf8"=>"✓", "authenticity_token"=>"hlx6K0p6sQVZl4yNAsDjIg2hX5XVU9zA9gYGweSIF6KrE5bLyrPFj8LQKrRYBsf9EzY6SgeL1woPXNN6XdRK6Q==", "answersheet"=>{"title"=>"", "answer_ids"=>[""]}, "1"=>"1", "3"=>"6", "4"=>"9", "5"=>"12", "commit"=>"Update Answersheet", "user_id"=>"2", "id"=>"1"}
>   User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1  [["id", 2]]
>   User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ?  ORDER BY "users"."id" ASC LIMIT 1  [["id", 2]]
>   Answersheet Load (0.1ms)  SELECT  "answersheets".* FROM "answersheets" WHERE "answersheets"."user_id" = ? LIMIT 1  [["user_id", 2]]
>    (0.1ms)  begin transaction
>   Answer Load (0.2ms)  SELECT "answers".* FROM "answers" INNER JOIN "qa_sheets" ON "answers"."id" = "qa_sheets"."answer_id" WHERE "qa_sheets"."answersheet_id" = ?  [["answersheet_id", 1]]
>   SQL (0.6ms)  DELETE FROM "qa_sheets" WHERE "qa_sheets"."answersheet_id" = ? AND "qa_sheets"."answer_id" IN (1, 6, 12)  [["answersheet_id", 1]]
>    (1.1ms)  commit transaction
> Redirected to http://localhost:3000/dashboard/users/2/answersheets/1
> Completed 302 Found in 14ms (ActiveRecord: 2.3ms)

之前錯誤的方法:
> <%= radio_button_tag "answersheet[answer_ids][]", answer.id, @answersheet.answer.include?(answer) %>

還有把值丟出去:“answer_ids”=>["", “18”]}

> Started PATCH "/dashboard/users/2/answersheets/1" for ::1 at 2015-11-29 21:38:34 +0800
> Processing by Dashboard::AnswersheetsController#update as HTML
>   Parameters: {"utf8"=>"✓", "authenticity_token"=>"LJURYnR/LHjXZyH0BQ2QQl/qmUbZY2CMJdyFlY5+akYB2v2C9LZY8kwgh81fy7SdQX38mQu7a0bchlAuNyI3DQ==", "answersheet"=>{"title"=>"", "answer_ids"=>["", "18"]}, "commit"=>"Update Answersheet", "user_id"=>"2", "id"=>"1"}
>   User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1  [["id", 2]]
>   User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ?  ORDER BY "users"."id" ASC LIMIT 1  [["id", 2]]
>   Answersheet Load (0.1ms)  SELECT  "answersheets".* FROM "answersheets" WHERE "answersheets"."user_id" = ? LIMIT 1  [["user_id", 2]]
>    (0.1ms)  begin transaction
>   Answer Load (0.2ms)  SELECT  "answers".* FROM "answers" WHERE "answers"."id" = ? LIMIT 1  [["id", 18]]
>   Answer Load (0.1ms)  SELECT "answers".* FROM "answers" INNER JOIN "qa_sheets" ON "answers"."id" = "qa_sheets"."answer_id" WHERE "qa_sheets"."answersheet_id" = ?  [["answersheet_id", 1]]
>   SQL (8.5ms)  DELETE FROM "qa_sheets" WHERE "qa_sheets"."answersheet_id" = ? AND "qa_sheets"."answer_id" IN (8, 69)  [["answersheet_id", 1]]
>   SQL (0.2ms)  INSERT INTO "qa_sheets" ("answersheet_id", "answer_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["answersheet_id", 1], ["answer_id", 18], ["created_at", "2015-11-29 13:38:34.190207"], ["updated_at", "2015-11-29 13:38:34.190207"]]
>    (1.1ms)  commit transaction
> Redirected to http://localhost:3000/dashboard/users/2/answersheets/1
> Completed 302 Found in 24ms (ActiveRecord: 10.5ms)

#7

改成這樣試試看吧 answersheet[answer_ids][#{question.id}]

但是這問題比較偏向html input tag of form的相關知識,可能稍微看一下你會更了解,不需要卡在rails method,畢竟產出來的是html code先把html用法掌握會比較理解。


#8

其實我在你的回復前已經試過了,> answersheet[answer_ids][#{question.id}]
他會出現
expected Hash (got Array) for param `answer_ids’

看了很多遍,看不出來個所以然…:pensive:


#9

出現在哪邊? params傳入的是否正確?
下面是我的demo

<input type="radio" name="flavour[ans][1]" id="choc" value="chocolate" />
<label for="choc">Chocolate</label><br/>
<input type="radio" name="flavour[ans][1]" id="cream" value="cream"/>
<label for="cream">Cream Filled</label><br/>
<input type="radio" name="flavour[ans][1]" id="honey" value="honey"/>
<label for="honey">Honey Glazed</label>
</p>
<p>
<input type="radio" name="flavour[ans][2]" id="choc" value="chocolate" />
<label for="choc">Chocolate</label><br/>
<input type="radio" name="flavour[ans][2]" id="cream" value="cream"/>
<label for="cream">Cream Filled</label><br/>
<input type="radio" name="flavour[ans][2]" id="honey" value="honey"/>
</p>


#10

想請問一下,

> <%= radio_button_tag "answersheet[answer_ids][]", answer.id, @answersheet.answer.include?(answer) %>

這樣的寫法,我記得在JC大的解釋中(params 的包裝與使用),
是把 answer.id丟到[answer_ids][]裡的[],然後在controller裡面有設定好permit,所以才可以存進去資料庫。

那如果像你的寫法:flavour[ans][1],我可以猜到[1]是指第一題,不過這樣chocolate 還可以丟進去?變成 1 => chocolate,

那不是會變成我之前寫的:
“answersheet”=>{“title”=>"", “answer_ids”=>[""]}, “1”=>“1”, “3”=>“6”, “4”=>“9”, “5”=>"12"
不就沒有辦法通過permit嗎?
(不過我上次PO的裡面也沒有出現permit沒過的警告)


#11

…此題,何棄療…

如果你有看過這篇

應該就知道其實你全都打 HTML 就 ok 的,其實不用 radio_button_tag 這種很難打的語法,產出 HTML 複製貼上一樣的意思,因為你用的(radio_button_tag => Rails Helper => 最終產出HTML)vs (最終產出的HTML or 手刻HTML)完全一樣的意思,所以反而你只填 HTML 再配上 params 就會清楚很多

再來 … 何需 permit,自幹就好,請見這篇

permit 的重點在於過濾,限制那些你不要的不能亂塞,但是你這邊全自幹,塞值自己來,自己過濾 params 取出你要的再塞進去,不就又快又漂亮又自由之類的?不要被那點小語法或是 Rails 內建的東西限制你的想像力,否則很可惜的,so~ 放手去幹唄

至於 permit 沒有出現沒過的訊息其實很正常,類似

params.require(:user).permit(:name)

配對的是

params #=> {:user => {:name => xxx}}

而你上面之前的變成

params #=> {:user => {:name => xxx} , "1"=>"1", "3"=>"6", "4"=>"9", "5"=>"12"}

看懂了嗎?params.require(:user) 的意思是把 params[:user] 這個 hash 丟進去做過濾,限制那個 hash 的內容只能用 :name,而非一同過濾類似 params["1"] , params["3"] ... 所以你看錯層了也沒餵對對象,不過如同上面我說的,全部自己來即可,寫個迴圈塞一塞,漂亮又簡單,以上


#12

老實說,資質有限,看不太懂。
我猜JC大的意思是說:
我應該先去把
> app / controllers / dashboard / answersheets_controller.rb
裡面,把這個刪掉:
> def answersheet_params
> params.require(:answersheet).permit(:title, :answer_ids =>[])
> end

然後去改create 跟 update 兩個method?

> create:
> @answersheet = @user.build_answersheet(answersheet_params) 改成
> @answersheet = @user.build_answersheet(:answer_id => params[:answersheet][:answer_ids])


> update:
> if @answersheet.update(answersheet_params) 改成
> if @answersheet.update(:answer_id => params[:answersheet][:answer_ids])

然後再回到VIEW (new 和 edit )那邊去改嗎?


#13

類似,不過上面你沒有貼出你的 schema 和 controller,所以不知道該如何幫忙起

我大概說明一下好了,如何新增與修改多對多或是一對多的部分,在同一個 view 上,你高興要做成打勾還是另外增加都可以,如同教學內教 jQuery 有示範的的動態表單,當持沒說如何儲存,而這邊只說明概念

         ItemChild(params name)
            ic[name][] , ic[age][] #將會有序成對的輸入
Item  <=    ic[name][] , ic[age][]
            ic[name][] , ic[age][]

例如一開始有三筆資料要進去,中間表手動塞入 A 的 id

所以新增的狀況會是

@item = Item.create(...)
params[:ic][:name].each_index do |index|
  #新增了10,11,12
  ItemChild.create(:item_id => @item.id , :name => params[:ic][:name][index] , :age => params[:ic][:age][index])
end

再來,你修改後送出可能會變成這樣子的

         ItemChild(params name)
            ic_old[10][name] , ic_old[10][age]
Item  <=    ic[name][] , ic[age][]
            ic_old[12][name] , ic_old[12][age]
            ic[name][] , ic[age][]
            #刪除ic_old[11]所以沒送出

簡單的來說你刪除了原先 id 的 11,然後再新增了兩筆新的

@item = Item.find(params[:id])

#取出所有關連id
ids = (params[:ic_old] || {}).keys.map{|i|i.to_i}

#將會刪除11,語意:排除送出的id之外的所有隸屬item的都刪除
if ids.length > 0
  ItemChild.where("item_id = ? AND id NOT IN (#{ids.join(',')})" , @item.id).delete_all
else
  ItemChild.where("item_id = ?" , @item.id).delete_all
end

#更新舊的資料
params[:ic_old].each_pair do |id , data|
  ic = ItemChild.where(:item_id => @item.id , :id => id).first
  if ic
    #這邊要過 permit 或是一個一個指定都行,前述
    ic.update_attributes(:name => data[:name] , :age => data[:age])
  end
end

#上面新增的code,額外新增的都再塞入
params[:ic][:name].each_index do |index|
  #新增了13,14
  ItemChild.create(:item_id => @item.id , :name => params[:ic][:name][index] , :age => params[:ic][:age][index])
end

推敲一下,大概就這樣子而已,再寫一個簡單的流程解釋上面的code

  1. Item先新增,讓ItemChild有隸屬的對象
  2. 增加三筆ItemChild(10,11,12)
  3. 因為Server未收到ItemChild(11),所以刪除它,而再新增兩筆ItemChild(13,14)

大概就這樣而已,其實就是舊集合(已經儲存的)和新集合(新送出的)的混合,而舊的都會帶id,新的沒有,這樣就可以達到一個完整的循環

而因為 [] 是單一值的array,所以比較麻煩,會需要多個 array,不過通常都是成對的輸入,所以你可以針對 params['ic']['name'] + params['ic']['age'] 來跑迴圈 ( 一個 index 跑 N 個 array ),而 ic_old[3] 本身就是 hash 了,本身就是一個值的集合,所以餵進去就好了

有這基本,下面多困難的結構都可以設計下去,而有問題再問沒關係,而真的看不懂就再說聲,我也寫一個測試用的 Project 給你們看 demo 好了?


#14

我這個練習有點混亂。:sweat:
麻煩大大們看一下~

schema:

> ActiveRecord::Schema.define(version: 20151115124952) do

>   create_table "answers", force: :cascade do |t|
>     t.integer  "question_id"
>     t.string   "content"
>     t.datetime "created_at",  null: false
>     t.datetime "updated_at",  null: false
>   end

>   create_table "answersheets", force: :cascade do |t|
>     t.string   "title"
>     t.datetime "created_at", null: false
>     t.datetime "updated_at", null: false
>     t.integer  "user_id"
>   end

>   create_table "managers", force: :cascade do |t|
>     t.string   "email",                  default: "", null: false
>     t.string   "encrypted_password",     default: "", null: false
>     t.string   "reset_password_token"
>     t.datetime "reset_password_sent_at"
>     t.datetime "remember_created_at"
>     t.integer  "sign_in_count",          default: 0,  null: false
>     t.datetime "current_sign_in_at"
>     t.datetime "last_sign_in_at"
>     t.string   "current_sign_in_ip"
>     t.string   "last_sign_in_ip"
>     t.datetime "created_at",                          null: false
>     t.datetime "updated_at",                          null: false
>   end

>   add_index "managers", ["email"], name: "index_managers_on_email", unique: true
>   add_index "managers", ["reset_password_token"], name: "index_managers_on_reset_password_token", unique: true

>   create_table "qa_sheets", force: :cascade do |t|
>     t.integer  "answersheet_id"
>     t.integer  "question_id"
>     t.integer  "answer_id"
>     t.datetime "created_at",     null: false
>     t.datetime "updated_at",     null: false
>   end

>   create_table "questionnaire_surveys", force: :cascade do |t|
>     t.string   "questionnaire_id"
>     t.string   "survey_id"
>     t.datetime "created_at",       null: false
>     t.datetime "updated_at",       null: false
>   end

>   create_table "questionnaires", force: :cascade do |t|
>     t.string   "name"
>     t.datetime "created_at", null: false
>     t.datetime "updated_at", null: false
>   end

>   create_table "questions", force: :cascade do |t|
>     t.integer  "survey_id"
>     t.text     "content"
>     t.datetime "created_at", null: false
>     t.datetime "updated_at", null: false
>   end

>   create_table "surveys", force: :cascade do |t|
>     t.string   "name"
>     t.datetime "created_at", null: false
>     t.datetime "updated_at", null: false
>   end

>   create_table "users", force: :cascade do |t|
>     t.string   "name"
>     t.datetime "created_at",                          null: false
>     t.datetime "updated_at",                          null: false
>     t.string   "email",                  default: "", null: false
>     t.string   "encrypted_password",     default: "", null: false
>     t.string   "reset_password_token"
>     t.datetime "reset_password_sent_at"
>     t.datetime "remember_created_at"
>     t.integer  "sign_in_count",          default: 0,  null: false
>     t.datetime "current_sign_in_at"
>     t.datetime "last_sign_in_at"
>     t.string   "current_sign_in_ip"
>     t.string   "last_sign_in_ip"
>     t.string   "avatar_file_name"
>     t.string   "avatar_content_type"
>     t.integer  "avatar_file_size"
>     t.datetime "avatar_updated_at"
>   end

>   add_index "users", ["email"], name: "index_users_on_email", unique: true
>   add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true

> end

answersheets_controller:

> class  Dashboard::AnswersheetsController < Dashboard::DashboardController 

>     before_action :find_user, only: [:show, :new, :edit, :create,:update] 
>     before_action :authenticate_user!, only: [:new, :edit, :create, :update, :destroy] 
>  

>    


> 	def show
> 		@answersheet = @user.answersheet
> 	end

> 	def new
>         
>         @answersheet = @user.build_answersheet
> 		
> 		@question = Question.all

> 	end
> 	 
> 	def edit
>         @answersheet = @user.answersheet
>         @question = Question.all
>         
> 	end

> 	def create
> 		@answersheet = @user.build_answersheet(answersheet_params)
> 		if @answersheet.save
>           redirect_to dashboard_user_answersheet_path(@user, @answersheet)
>         else
>           render :new
>         end
> 	end

> 	def update
> 	   @answersheet = @user.answersheet

> 	   if @answersheet.update(answersheet_params)
> 	     redirect_to dashboard_user_answersheet_path, notice: "文章修改成功!"
> 	   else
> 	     render :edit
> 	  end
> 	end

> 	def destroy
> 	end
> private
>     
>     def answersheet_params
>       params.require(:answersheet).permit(:title, :answer_ids =>[])
>     end

>     private

>   def find_user
>       @user = User.find(params[:user_id])
>   end  

> end

有DEMO可以看當然最好啦~:laughing:


#15

其實我昨天下班無聊就寫完了||| and 來開新主題


#16

感恩~
已經看了整個早上,
可是還不知道應用到我自己的東西,還在研究怎麼改。:sweat_smile: