5

Sorunum, accepts_nested_attributes_for'un sınırlamalarına girdiğimden, daha fazla esnekliğe sahip olmak için bu işlevselliği kendi başıma nasıl kopyalayacağımı bulmam gerekiyor. (Beni tam olarak neyin asılı tuttuğuna bakmak için aşağıya bakın.) Bu yüzden sorum: Fors_nested_attributes_for'u taklit etmek ve büyütmek istiyorsam formum, denetleyici ve modellerim nasıl görünmeli? Gerçek hile, mevcut VE yeni modelleri mevcut ilişkilendirmeler/özniteliklerle güncelleyebilmem gerekiyor.Raylar - accepts_nested_attributes_for'u kullanmadan iç içe öznitelikleri nasıl yönetilir?

Yuvalanmış formlar kullanan bir uygulama yapıyorum. İlk olarak bu RailsCast'i bir plan olarak kullandım (accepts_nested_attributes_for): Railscast 196: Nested Model Form.

Uygulamam, işler (görevler) içeren denetim listeleridir ve kullanıcının denetim listesini (adı, açıklaması) güncelleştirmesini ve ilişkili işleri tek bir biçimde ekle/kaldırmasına izin veriyorum. Bu iyi çalışıyor, ancak bunu uygulamamın başka bir yönüne dahil ettiğimde sorun yaşıyorum: sürüm oluşturma yoluyla geçmiş.

Uygulamamın büyük bir kısmı, modelleri ve ilişkilendirmelerim için geçmiş bilgileri kaydetmem gerektiğidir. Kendi versiyonumuzu hazırladım (here benim karar sürecimi/düşüncelerimi tanımladığım bir sorudur) ve bunun büyük bir kısmı eski bir şeyin yeni bir versiyonunu oluşturmam gereken, yeni versiyona güncellemeler yapmam gereken bir iş akışı. eski sürümü arşivleyin. Bu, deneyimi UI aracılığıyla bir modeli güncellemek olarak gören kullanıcıyı görmezden geliyor.

Kodu - modeller

#checklist.rb 
class Checklist < ActiveRecord::Base 
    has_many :jobs, :through => :checklists_jobs 
    accepts_nested_attributes_for :jobs, :reject_if => lambda { |a| a[:name].blank? }, :allow_destroy => true 
end 

#job.rb 
class Job < ActiveRecord::Base 
    has_many :checklists, :through => :checklists_jobs 
end 

Kodu - Geçerli formu (NOT: @jobs kontrol listeleri kontrolör düzenleme eylem bu denetim için arşivden işler olarak tanımlanır; bu yüzden @checklist olan)

<%= simple_form_for @checklist, :html => { :class => 'form-inline' } do |f| %> 
    <fieldset> 
    <legend><%= controller.action_name.capitalize %> Checklist</legend><br> 

    <%= f.input :name, :input_html => { :rows => 1 }, :placeholder => 'Name the Checklist...', :class => 'autoresizer' %> 
    <%= f.input :description, :input_html => { :rows => 3 }, :placeholder => 'Optional description...', :class => 'autoresizer' %> 

    <legend>Jobs on this Checklist - [Name] [Description]</legend> 

    <%= f.fields_for :jobs, @jobs, :html => { :class => 'form-inline' } do |j| %> 
     <%= render "job_fields_disabled", :j => j %> 
    <% end %> 
    </br> 
    <p><%= link_to_add_fields "+", f, :jobs %></p> 

    <div class="form-actions"> 
     <%= f.submit nil, :class => 'btn btn-primary' %> 
     <%= link_to 'Cancel', checklists_path, :class => 'btn' %> 
    </div> 
    </fieldset> 
<% end %> 

Code - snippet from checklists_controller.rb # Güncelle

Ve işte burada çalışıyorum accepts_nested_attributes_for sınırlaması için nto (oldukça iyi bir şekilde belgeleniyor here. Temel olarak tasarlandığı şekliyle "Model1 için ID = X ile Model1 bulunamadı;" ID = Y "istisnası aldım.

Peki, birden çok iç içe geçmiş model oluşturabilir ve üst modelin formunu kabulprefined_attributes_içine benzer şekilde ekleyebilir veya kaldırabilirim, ancak kendi başıma?

Gördüğüm seçenekler - bunlardan en iyisi mi? Gerçek hile, mevcut VE yeni modelleri mevcut ilişkilendirmeler/özniteliklerle güncelleyebilmem gerekir. Onları birbirine bağlayamıyorum, bu yüzden onları adlandıracağım.

Redtape (github üzerinde) Virtus (aynı zamanda github) Yardımlarınız için

teşekkürler!

+0

Bunu çözdüyseniz, çözümünüzü görmeyi çok isterim. –

+0

Mario, Bunu çözdüm ve kodumu aşağıda yayınladım. Bu harika bir kod değil, ama benzer bir şeyle uğraşıyorsanız, belki size bazı fikirler verecektir. Herhangi bir sorunuz varsa, sadece burayı veya cevabım hakkında yorum yazabilirim ve yapabileceklerimi açıklamaya çalışacağım. – JoshDoody

cevap

1

bazı yararlı şeyler var.

Bunun çok zarif bir çözüm olmadığından emin olduğumu söylemeliyim ve bu harika bir kod değil. Ama ben onunla geldim ve işe yarıyor. Bu soru oldukça teknik olduğundan, burada sahte kod yayınlamıyorum - Hem Denetim Listesi modeli hem de Denetim Listeleri denetleyicisi güncelleştirme eylemi için (bu soruna uygulanacak kod bölümleri) tam kodu yayınlıyorum. Ayrıca, işlem bloklarımın aslında hiçbir şey yapmadığından eminim (bunları düzeltmem gerekiyor).

Temel fikir, güncelleştirme eylemini el ile çözdüm. Aksine update_attributes (ve accepts_nested_attributes_for) güvenmek yerine, elle iki aşamada kontrol listesi güncelleme:

  1. fiili kontrol listesi nesnesi mi değişti (bir kontrol listesi yalnızca bir ad ve açıklama vardır)? Öyleyse, yeni bir kontrol listesi oluşturun, yenisini eskisinden bir çocuk yapın ve yeni olanı ne için eklendiyse veya bunun için seçiliyse ayarlayın.
  2. Kontrol listesi değişmemişse (isim ve açıklama aynı kaldı), kendisine atanan işler değişti mi? Varsa, kaldırılan iş atamalarını arşivleyin ve yeni iş atamalarını ekleyin.

Burada görmezden güvenli olduğunu düşünüyorum bazı "teslim" işler var (o kontrol listesi nasıl değiştiğini bile önemli olmadığını belirlemek için temelde mantık - Herhangi gönderimler bulunmuyorsa (bir kontrol listesi tarihsel verilerin kayıtları) Ardından, bu arşivlemeyi yapmadan ya da işleri çıkartarak/çıkararak kontrol listesini yerinde güncellemeniz yeterlidir).

Bunun yardımcı olup olmayacağını bilmiyorum, ama işte burada zaten var.

Kodu - checklist.rb (model)

class Checklist < ActiveRecord::Base 
    scope :archived_state, lambda {|s| where(:archived => s) } 

    belongs_to :creator, :class_name => "User", :foreign_key => "creator_id" 
    has_many :submissions 
    has_many :checklists_jobs, :dependent => :destroy, :order => 'checklists_jobs.job_position'#, :conditions => {'archived_at' => nil} 
    has_many :jobs, :through => :checklists_jobs 
    has_many :unarchived_jobs, :through => :checklists_jobs, 
      :source => :job, 
      :conditions => ['checklists_jobs.archived = ?', false], :order => 'checklists_jobs.job_position' 
    has_many :checklists_workdays, :dependent => :destroy 
    has_many :workdays, :through => :checklists_workdays 

    def make_child_of(old_checklist) 
    self.parent_id = (old_checklist.parent_id == 0) ? old_checklist.id : old_checklist.parent_id 
    self.predecessor_id = old_checklist.id 
    self.version = (old_checklist.version + 1) 
    end 

    def set_new_jobs(new_jobs) 
    new_jobs.to_a.each do |job| 
     self.unarchived_jobs << Job.find(job) unless job.nil? 
    end 
    end 

    def set_jobs_attributes(jobs_attributes, old_checklist) 
    jobs_attributes.each do |key, entry| 
     # Job already exists and should have a CJ 
     if entry[:id] && !(entry[:_destroy] == '1') 
     old_cj = old_checklist.checklists_jobs.archived_state(:false).find_by_job_id(entry[:id]) 
     new_cj = ChecklistsJob.new job_position: old_cj.job_position, job_required: old_cj.job_required 
     new_cj.checklist = self 
     new_cj.job = old_cj.job 
     new_cj.save! 
     # New job, should be created and added to new checklist only 
     else 
     unless entry[:_destroy] == '1' 
     entry.delete :_destroy 
     self.jobs << Job.new(entry) 
     end 
     end 
    end 
    end 

    def set_checklists_workdays!(old_checklist) 
    old_checklist.checklists_workdays.archived_state(:false).each do |old_cw| 
     new_cw = ChecklistsWorkday.new checklist_position: old_cw.checklist_position 
     new_cw.checklist = self 
     new_cw.workday = old_cw.workday 
     new_cw.save! 
     old_cw.archive 
     old_cw.save! 
    end 
    end 

    def update_checklists_jobs!(jobs_attributes) 
    jobs_attributes.each do |key, entry| 
     if entry[:id] # Job was on self when #edit was called 
     old_cj = self.checklists_jobs.archived_state(:false).find_by_job_id(entry[:id]) 
     #puts "OLD!! "+old_cj.id.to_s 
     unless entry[:_destroy] == '1' 
      new_cj = ChecklistsJob.new job_position: old_cj.job_position, job_required: old_cj.job_required 
      new_cj.checklist = self 
      new_cj.job = old_cj.job 
      new_cj.save! 
     end 
     old_cj.archive 
     old_cj.save! 
     else # Job was created on this checklist 
     unless entry[:_destroy] == '1' 
      entry.delete :_destroy 
      self.jobs << Job.new(entry) 
     end 
     end 
    end 
    end 
end 

Kodu - checklists_controller.rb (kontrol)

class ChecklistsController < ApplicationController 
    before_filter :admin_user 

    def update 
    @checklist = Checklist.find(params[:id]) 
    @testChecklist = Checklist.find(params[:id]) 
    @oldChecklist = Checklist.find(params[:id]) 
    @job_list = @checklist.unarchived_jobs.exists? ? Job.archived_state(:false).where('id not in (?)', @checklist.unarchived_jobs) : Job.archived_state(:false) 

    checklist_ok = false 
    # If the job is on a submission, do archiving/copying; else just update it 
    if @checklist.submissions.count > 0 
     puts "HERE A" 
     # This block will tell me if I need to make new copies or not 
     @testChecklist.attributes=(params[:checklist]) 
     jobs_attributes = params[:checklist][:jobs_attributes] 
     if @testChecklist.changed? 
     puts "HERE 1" 
     params[:checklist].delete :jobs_attributes   
     @newChecklist = Checklist.new(params[:checklist]) 
     @newChecklist.creator = current_user 
     @newChecklist.make_child_of(@oldChecklist) 
     @newChecklist.set_new_jobs(params[:new_jobs]) 

     begin 
      ActiveRecord::Base.transaction do 
      @newChecklist.set_jobs_attributes(jobs_attributes, @oldChecklist) if jobs_attributes 
      @newChecklist.set_checklists_workdays!(@oldChecklist) 
      @newChecklist.save! 
      @oldChecklist.archive 
      @oldChecklist.save! 
      @checklist = @newChecklist 
      checklist_ok = true 
      end 
      rescue ActiveRecord::RecordInvalid 
      # This is a NEW checklist, so it's acting like it's "new" - WRONG? 
      puts "RESCUE 1" 
      @checklist = @newChecklist 
      @jobs = @newChecklist.jobs  
      checklist_ok = false 
     end    
     elsif @testChecklist.changed_for_autosave? || params.has_key?(:new_jobs) 
     puts "HERE 2"  
     # Associated Jobs have changed, so archive old checklists_jobs, 
     # then set checklists_jobs based on params[:checklist][:jobs_attributes] and [:new_jobs] 

     @checklist.set_new_jobs(params[:new_jobs]) 

     begin 
      ActiveRecord::Base.transaction do 
      @checklist.update_checklists_jobs!(jobs_attributes) if jobs_attributes 
      @checklist.save! 
      checklist_ok = true 
      end 
      rescue ActiveRecord::RecordInvalid  
      puts "RESCUE 2" 
      @jobs = @checklist.unarchived_jobs 
      checklist_ok = false 
     end 
     else 
     checklist_ok = true # There were no changes to the Checklist or Jobs 
     end 
    else 
     puts "HERE B" 
     @checklist.set_new_jobs(params[:new_jobs]) 
     begin 
     ActiveRecord::Base.transaction do 
      @checklist.update_attributes(params[:checklist]) 
      checklist_ok = true 
     end 
     rescue ActiveRecord::RecordInvalid 
     puts "RESCUE B" 
     @jobs = @checklist.jobs  
     checklist_ok = false 
     end 
    end 

    respond_to do |format| 
     if checklist_ok 
     format.html { redirect_to @checklist, notice: 'List successfully updated.' } 
     format.json { head :no_content } 
     else 
     flash.now[:error] = 'There was a problem updating the List.' 
     format.html { render action: "edit" } 
     format.json { render json: @checklist.errors, status: :unprocessable_entity } 
     end 
    end 
    end 
end 

Kodu - Kontrol Listesi formu

<%= form_for @checklist, :html => { :class => 'form-inline' } do |f| %> 
    <div> 
    <%= f.text_area :name, :rows => 1, :placeholder => 'Name the list...', :class => 'autoresizer checklist-name' %></br> 
    <%= f.text_area :description, :rows => 1, :placeholder => 'Optional description...', :class => 'autoresizer' %> 
    </div> 

    <%= f.fields_for :jobs, :html => { :class => 'form-inline' } do |j| %> 
    <%= render "job_fields", :j => j %> 
    <% end %> 

    <span class="add-new-job-link"><%= link_to_add_fields "add a new job", f, :jobs %></span> 
    <div class="form-actions"> 
    <%= f.submit nil, :class => 'btn btn-primary' %> 
    <%= link_to 'Cancel', checklists_path, :class => 'btn' %> 
    </div> 

    <% unless @job_list.empty? %> 
    <legend>Add jobs from the Job Bank</legend> 

    <% @job_list.each do |job| %> 
     <div class="toggle"> 
     <label class="checkbox text-justify" for="<%=dom_id(job)%>"> 
      <%= check_box_tag "new_jobs[]", job.id, false, id: dom_id(job) %><strong><%= job.name %></strong> <small><%= job.description %></small> 
     </label> 
     </div> 
    <% end %> 

    <div class="form-actions"> 
     <%= f.submit nil, :class => 'btn btn-primary' %> 
     <%= link_to 'Cancel', checklists_path, :class => 'btn' %> 
    </div> 
    <% end %> 
<% end %> 
+0

IMO, '_destroy' paramlarını doğrudan manipüle etmekten kaçınmalısınız. Bence bu, müşterinin işi javascript üzerinden yapabilmesi için kanı akan bir uygulama detayı olarak düşünüyorum. Sunucuda, "mark_for_destruction" ve "marked_for_destruction?" Ifadelerini kullanın. Bkz. Http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html –

5

Muhtemelen kabul edilen istekleri kopyalamanız ve gerekli tüm adımları içerecek şekilde özel bir sınıf veya modül oluşturmanız gerekecektir.Mario sorumu yorumladı ve bunu çözdü sordu yana

benim çözüm paylaşmak istedim, bu yazı

http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/

Özellikle noktası 3

+0

Sanırım haklısın. Daha önce blog yazısı gördük ve bence yukarıda bahsettiğim redtape gem'e ilham kaynağı oldu. Başka bir yerlere sordum ve kırmızı bant bir kaç kez geldi, belki de gitmem gereken yer burası. – JoshDoody

İlgili konular