over 2 years ago

來紀錄一下我的後台是怎麼新增Cate(類別), Issue(題目), Option(選項)。

剛開始的時候,因為以為題目(Issue)只會屬於一個類別(Cate),所以寫成has_many/belongs_to的關係。

不過,後來想到,如果有些題目要設成:重要題/基本題/特殊題/featured,又或者,要在不同的頁面出現,譬如:登入頁面 / 一般頁面等等可能,可能一個題目就要有不同的類別,所以決定要改成has_many, through:的做法。
(has_many, through:比HABTM好的理由看這裡)

但是,要有多重類別,又有不同的做法。
最簡單的做法:應該是讓一個issue有很多個attribute,譬如cate_1、cate_2、cate_3、cate_4、cate_5、cate_6,然後每一個都放進去一個的cate。
不過好像有點蠢。因為以後如果有新增的,就必須要一直rails g migration add_cate_n_to_issue_table。

所以比較好的做法應該有兩種(利用has_many, through:),但是我還沒想出來哪個比較好:

  • 第一種做法:

  • 第二種做法:等於是最簡單的做法加上第一種做法

    • 一個題目有兩個(或以上的)category columns(attributes)
    • 一個可以叫ordinary category,一個可以叫special category
    • 也是可以選擇要不要用{:multiple => true}

備註:目前為止,還沒有辦法找到用rails helper(nested: fields_for)(好像不能用在has_many::through這樣的關係上)同時傳送出多個題目的答案的方法,因為id都會重複,必須要用html tag的方式去解決,詳細情況請看這篇:用select tag 一次回答多個問題

  1. 舊的:題目只會屬於一個類別,所以寫成has_many/belongs_to。

    surveysays / app / models / cate.rb
    class Cate < ActiveRecord::Base
    has_many :issue#, :dependent => :destroy
    
    end
    
    surveysays / app / models / issue.rb
    class Issue < ActiveRecord::Base
    belongs_to :cate
    has_many :option, :dependent => :destroy
    has_many :respond, through: :option
    has_many :user, through: :respond
    
    accepts_nested_attributes_for :option, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
    end
    

    重要:select的寫法:(這邊可能寫錯了,怎麼會沒有f.)
    <%= select('issue', 'cate_id', Cate.all.collect {|p| [ p.name, p.id ] }, { include_blank: true }, {:class =>"form-control"})%>

    surveysays / app / views / dashboard / admin / issues / new.html.erb
    <%= form_for @issue, :url => dashboard_admin_issues_path, :method => :POST do |f| %>
                <div class="edit_panel panel panel-default">
                    <div class="edit_heading panel-heading">
                        <h3 class="panel-title">建新問題</h3>
          </div>
          <div class="panel-body">
            <div class='option-content'>
                <div class="form-group">
                    <%= label_tag "類別:" %> <%= select('issue', 'cate_id', Cate.all.collect {|p| [ p.name, p.id ] }, { include_blank: true }, {:class =>"form-control"})%>
              </div>
              <div class="form-group">
                                <%= label_tag "題目:" %> <%= f.text_field :content, class:"form-control" %>
                            </div>
                            <div class="form-inline">
                                <div class="form-group">
                                    <%= f.fields_for :option do |option| %>
                                        <p>
                                        <%= label_tag "選項:" %>
                                        <%= option.text_field :content, class:"form-control" %>
                                        <%= option.check_box :_destroy, class:"checkbox" %><%= label_tag "刪除" %>
                                        <%#= option.link_to_remove "Remove this Answer" %>
                                        </p>
                                    <% end %>
                                </div>
                            </div>   
            </div>
            <div class="form-group">
                <%= f.submit "送出", class: "btn btn-primary" %>
            </div>
           </div>
          </div>
        <% end %>
    

    注意:這邊是:cate_id,因為只有一個cate_id會傳出去。

    surveysays / app / controllers / dashboard / admin / issues_controller.rb
    private
    def issue_params
        params.require(:issue).permit(:content, :cate_id, option_attributes:[:issue_id, :id, :content,:_destroy])
    end
    
  2. 第一種做法:要新增一個cate_issue.rb,然後

    surveysays / app / models / cate.rb
    class Cate < ActiveRecord::Base
    has_many :cate_issue
    has_many :issue, through: :cate_issue
    end
    
    surveysays / app / models / issue.rb (把belongs_to :cate注記掉)
    class Issue < ActiveRecord::Base
    has_many :cate_issue
    has_many :cate, through: :cate_issue
    
    #belongs_to :cate
    
    has_many :option, :dependent => :destroy
    has_many :respond, through: :option
    has_many :user, through: :respond
    
    accepts_nested_attributes_for :option, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
    end
    
    surveysays / app / models / cate_issue.rb
    class CateIssue < ActiveRecord::Base
    belongs_to :cate
    belongs_to :issue
    end
    

    注意:這邊記得要改成:cate_ids => [],因為會傳出去很多個cate ids

    surveysays / app / controllers / dashboard / admin / issues_controller.rb
    private
    def issue_params
        params.require(:issue).permit(:content, :cate_ids => [], option_attributes:[:issue_id, :id, :content,:_destroy])
    end
    

    <%= label_tag "類別:" %> <%= f.select :cate_ids, Cate.all.collect {|p| [ p.name, p.id ] }, { }, {:class =>"form-control", :multiple => true} %>

    surveysays / app / views / dashboard / admin / issues / new.html.erb
    <%= form_for @issue, :url => dashboard_admin_issues_path, :method => :POST do |f| %>
                <div class="edit_panel panel panel-default">
                    <div class="edit_heading panel-heading">
                        <h3 class="panel-title">
                            建新問題
                        </h3>
          </div>
          <div class="panel-body">
            <div class='option-content'>
              <div class="form-group">
                    <%= label_tag "類別:" %> 
                <%= f.select :cate_ids, Cate.all.collect {|p| [ p.name, p.id ] }, { }, {:class =>"form-control", :multiple => true} %>
              </div>
              <div class="form-group">
                                <%= label_tag "題目:" %> 
                <%= f.text_field :content, class:"form-control" %>
                            </div>
                            <div class="form-inline">
                                <div class="form-group">
                                    <%= f.fields_for :option do |option| %>
                                        <p>
                                        <%= label_tag "選項:" %>
                                        <%= option.text_field :content, class:"form-control" %>
                                        <%= option.check_box :_destroy, class:"checkbox" %>
                    <%= label_tag "刪除" %>
                                        <%#= option.link_to_remove "Remove this Answer" %>
                                        </p>
                                    <% end %>
                                </div>
                            </div>   
            </div>
            <div class="form-group">
                <%= f.submit "送出", class: "btn btn-primary" %>
            </div>
           </div>
          </div>
        <% end %>
    
  3. 第二種做法:要新增一個cate_issue.rb(應該要改個名字比較好,不過還沒想到)跟另一個category的名字。
    記得:改成OrdinaryCategory.rb,SpecialCategory.rb,Issue.rb,CateIssue.rb (先保留Cate.rb)
    記得:改routes,要在cateissue table 新增ordinary_cate_id 跟special_cate_id

    surveysays / app / models / cate.rb
    class Cate < ActiveRecord::Base
    has_many :cate_issue
    has_many :issue, through: :cate_issue
    end
    
    surveysays / app / models / ordinary_category.rb
    class OrdinaryCate < ActiveRecord::Base
    has_many :cate_issue
    has_many :issue, through: :cate_issue
    end
    
    surveysays / app / models / special_category.rb
    class SpecialCate < ActiveRecord::Base
    has_many :cate_issue
    has_many :issue, through: :cate_issue
    end
    
    surveysays / app / models / issue.rb
    class Issue < ActiveRecord::Base
    has_many :cate_issue
    has_many :cate, through: :cate_issue
    has_many :ordinary_cate, through: :cate_issue
    has_many :special_cate, through: :cate_issue
    
    #belongs_to :cate
    
    has_many :option, :dependent => :destroy
    has_many :respond, through: :option
    has_many :user, through: :respond
    
    accepts_nested_attributes_for :option, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
    end
    
    surveysays / app / models / cate_issue.rb
    class CateIssue < ActiveRecord::Base
    belongs_to :ordinary_cate
    belongs_to :specail_cate
    belongs_to :cate
    belongs_to :issue
    end
    

    注意:這邊記得要改成:cate_ids => [],因為會傳出去很多個cate ids

    surveysays / app / controllers / dashboard / admin / issues_controller.rb
    private
    def issue_params
        params.require(:issue).permit(:content, :cate_ids => [],:ordinary_cate_ids => [],:special_cate_ids => [], option_attributes:[:issue_id, :id, :content,:_destroy])
    end
    
    surveysays / app / views / dashboard / admin / issues / new.html.erb
    <%= form_for @issue, :url => dashboard_admin_issues_path, :method => :POST do |f| %>
                <div class="edit_panel panel panel-default">
                    <div class="edit_heading panel-heading">
                        <h3 class="panel-title">
                            建新問題
                        </h3>
          </div>
          <div class="panel-body">
            <div class='option-content'>
              <div class="form-group">
                    <%= label_tag "類別:" %> <%= f.select :cate_ids, Cate.all.collect {|p| [ p.name, p.id ] }, { }, {:class =>"form-control", :multiple => true} %>
              </div>
              <div class="form-group">
                    <%= label_tag "一般類別:" %> <%= f.select :ordinary_cate_ids, OrdinaryCate.all.collect {|p| [ p.name, p.id ] }, { }, {:class =>"form-control", :multiple => true} %>
              </div>
              <div class="form-group">
                    <%= label_tag "特殊類別:" %> <%= f.select :special_cate_ids, SpecialCate.all.collect {|p| [ p.name, p.id ] }, { }, {:class =>"form-control", :multiple => true} %>
              </div>
              <div class="form-group">
                                <%= label_tag "題目:" %> <%= f.text_field :content, class:"form-control" %>
                            </div>
                            <div class="form-inline">
                                <div class="form-group">
                                    <%= f.fields_for :option do |option| %>
                                        <p>
                                        <%= label_tag "選項:" %>
                                        <%= option.text_field :content, class:"form-control" %>
                                        <%= option.check_box :_destroy, class:"checkbox" %><%= label_tag "刪除" %>
                                        <%#= option.link_to_remove "Remove this Answer" %>
                                        </p>
                                    <% end %>
                                </div>
                            </div>   
            </div>
            <div class="form-group">
                <%= f.submit "送出", class: "btn btn-primary" %>
            </div>
           </div>
          </div>
        <% end %>
    

    注意:這邊寫法要注意

    surveysays / app / controllers / dashboard / admin / ordinary_cates_controller.rb
    private
     def ordinarycate_params
         params.require(:ordinary_cate).permit(:name)
     end
    
    surveysays / app / controllers / dashboard / admin / special_cates_controller.rb
    private
     def specialcate_params
         params.require(:special_cate).permit(:name)
     end
    
← has_many :through 回答問題的模式 (後台跟前台) →