5

मेरी समस्या यह है कि मैंने accepts_nested_attributes_for की सीमाओं में भाग लिया है, इसलिए मुझे यह समझने की आवश्यकता है कि अधिक कार्यक्षमता प्राप्त करने के लिए मुझे उस कार्यक्षमता को दोहराने के लिए कैसे करें। (मुझे नीचे लटकने के लिए नीचे देखें।) तो मेरा सवाल यह है कि: मेरा फॉर्म, कंट्रोलर और मॉडल कैसा दिखना चाहिए यदि मैं नकल करना चाहता हूं और accepts_nested_attributes_for बढ़ा सकता हूं? असली चाल है कि मुझे मौजूदा एसोसिएशन/विशेषताओं के साथ मौजूदा और नए मॉडल दोनों को अपडेट करने में सक्षम होना चाहिए।रेल - accepts_nested_attributes_for का उपयोग किये बिना नेस्टेड विशेषताओं का प्रबंधन कैसे करें?

मैं एक ऐप बना रहा हूं जो नेस्टेड रूपों का उपयोग करता है। मैंने शुरुआत में इस रेलस्कास्ट को ब्लूप्रिंट के रूप में उपयोग किया (accepts_nested_attributes_for लीवरेजिंग): Railscast 196: Nested Model Form

मेरा ऐप नौकरियों (कार्यों) के साथ चेकलिस्ट है, और मैं उपयोगकर्ता को चेकलिस्ट (नाम, विवरण) अपडेट करने और एक ही रूप में संबंधित नौकरियों को जोड़ने/निकालने की अनुमति दे रहा हूं। यह अच्छी तरह से काम करता है, लेकिन जब मैं इसे अपने ऐप के किसी अन्य पहलू में शामिल करता हूं तो मैं समस्याओं में भाग लेता हूं: संस्करण के माध्यम से इतिहास।

मेरे ऐप का एक बड़ा हिस्सा यह है कि मुझे अपने मॉडल और एसोसिएशन के लिए ऐतिहासिक जानकारी रिकॉर्ड करने की आवश्यकता है। मैंने अपना खुद का संस्करण रोलिंग समाप्त कर दिया (here मेरा प्रश्न है जहां मैं अपनी निर्णय प्रक्रिया/विचारों का वर्णन करता हूं), और इसका एक बड़ा हिस्सा वर्कफ़्लो है जहां मुझे पुरानी चीज़ का नया संस्करण बनाना है, नए संस्करण में अपडेट करना है पुराने संस्करण को संग्रहित करें। यह उपयोगकर्ता के लिए अदृश्य है, जो यूआई के माध्यम से एक मॉडल को अद्यतन करने के अनुभव को देखता है।

कोड - मॉडल

#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 

कोड - मौजूदा फार्म (नोट: @jobs जाँच सूची नियंत्रक कार्रवाई संपादन में इस चेकलिस्ट के लिए संग्रह से निकाला गया नौकरियों के रूप में परिभाषित किया गया है, तो @checklist है)

<%= 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 %> 

कोड - checklists_controller.rb से स्निपेट # अद्यतन

def update 
    @oldChecklist = Checklist.find(params[:id]) 

# Do some checks to determine if we need to do the new copy/archive stuff 
    @newChecklist = @oldChecklist.dup 
    @newChecklist.parent_id = (@oldChecklist.parent_id == 0) ? @oldChecklist.id : @oldChecklist.parent_id 
    @newChecklist.predecessor_id = @oldChecklist.id 
    @newChecklist.version = (@oldChecklist.version + 1) 
    @newChecklist.save 

# Now I've got a new checklist that looks like the old one (with some updated versioning info). 

# For the jobs associated with the old checklist, do some similar archiving and creating new versions IN THE JOIN TABLE 
    @oldChecklist.checklists_jobs.archived_state(:false).each do |u| 
    x = u.dup 
    x.checklist_id = @newChecklist.id 
    x.save 
    u.archive 
    u.save 
    end 

# Now the new checklist's join table entries look like the old checklist's entries did 
# BEFORE the form was submitted; but I want to update the NEW Checklist so it reflects 
# the updates made in the form that was submitted. 
# Part of the params[:checklist] has is "jobs_attributes", which is handled by 
# accepts_nested_attributes_for. The problem is I can't really manipulate that hash very 
# well, and I can't do a direct update with those attributes on my NEW model (as I'm 
# trying in the next line) due to a built-in limitation. 
    @newChecklist.update_attributes(params[:checklist]) 

और जहां मैं चलाता हूं accepts_nested_attributes_for सीमा के लिए (यह बहुत अच्छी तरह से here दस्तावेज है। मुझे "आईडी = वाई" अपवाद के साथ मॉडल 2 के लिए आईडी = एक्स के साथ मॉडल 1 नहीं मिला, जो मूल रूप से डिज़ाइन किया गया है।

तो, मैं कई नेस्टेड मॉडल कैसे बना सकता हूं और उन्हें मूल मॉडल के रूप में जोड़/हटा सकता हूं जो accepts_nested_attributes_for करता है, लेकिन स्वयं ही?

मैंने जो विकल्प देखा है - इनमें से एक सबसे अच्छा है? वास्तविक चाल मुझे मौजूदा संगठनों/विशेषताओं के साथ मौजूदा और नए मॉडल दोनों को अपडेट करने में सक्षम होने की आवश्यकता है। मैं उन्हें लिंक नहीं कर सकता, इसलिए मैं उन्हें नाम दूंगा।

Redtape (GitHub पर) Virtus (भी GitHub)

आपकी मदद के लिए धन्यवाद!

+0

यदि आपने इसे हल किया है तो मुझे आपका समाधान देखने में बहुत दिलचस्पी होगी। –

+0

मारियो, मैंने इसे हल किया, और मैंने नीचे अपना कोड पोस्ट किया। यह बहुत अच्छा कोड नहीं है, लेकिन यदि आप कुछ इसी तरह से संघर्ष कर रहे हैं, तो शायद यह आपको कुछ विचार देगा। कोई सवाल, बस यहां या मेरे उत्तर पर टिप्पणी करें और यदि मैं कर सकता हूं तो मैं स्पष्ट करने की कोशिश करूंगा। – JoshDoody

उत्तर

1

में कुछ उपयोगी सामान है मैंने सोचा था कि मैं अपने समाधान का हिस्सा होगा।

मुझे यह कहना चाहिए कि मुझे यकीन है कि यह एक बहुत ही सुरुचिपूर्ण समाधान नहीं है, और यह बहुत अच्छा कोड नहीं है। लेकिन यह वही है जो मैं आया था, और यह काम करता है। चूंकि यह प्रश्न बहुत तकनीकी है, इसलिए मैं यहां छद्म कोड पोस्ट नहीं कर रहा हूं - मैं चेकलिस्ट मॉडल और चेकलिस्ट नियंत्रक अद्यतन कार्रवाई (कोड के उन हिस्सों के लिए जो भी इस प्रश्न पर लागू होता है) के लिए पूर्ण कोड पोस्ट कर रहा हूं। मुझे भी यकीन है कि मेरे लेनदेन ब्लॉक वास्तव में कुछ भी नहीं कर रहे हैं (मुझे उनको ठीक करने की ज़रूरत है)।

मूल विचार है कि मैंने अद्यतन कार्रवाई को मैन्युअल रूप से तोड़ दिया है। बल्कि update_attributes (और accepts_nested_attributes_for) पर भरोसा करने की बजाय, मैं मैन्युअल रूप से दो चरणों में जांच-सूची को अद्यतन:

  1. वास्तविक चेकलिस्ट वस्तु में परिवर्तन किया था (एक चेकलिस्ट केवल एक नाम और विवरण नहीं है)? यदि ऐसा होता है, तो एक नई चेकलिस्ट बनाएं, नया बच्चा पुराना एक बच्चा बनाएं, और इसके लिए जो भी जॉब जोड़ा गया या उसके लिए चुना गया हो, उसे नया सेट करें।
  2. यदि चेकलिस्ट स्वयं नहीं बदली (नाम और विवरण वही रहा), तो क्या इसे सौंपा गया कार्य बदल गया? यदि उन्होंने किया, तो हटाए गए नौकरी असाइनमेंट को संग्रहीत करें, और कोई भी नया जॉब असाइनमेंट जोड़ें।

कुछ "प्रस्तुत" सामान मुझे लगता है कि यहाँ की अनदेखी करने के लिए सुरक्षित है नहीं है (यह मूल रूप से तर्क है निर्धारित करने के लिए करता है, तो यह और भी मायने रखती है कि कैसे चेकलिस्ट बदल गया है - अगर कोई प्रस्तुतियाँ नहीं हैं (एक चेकलिस्ट के ऐतिहासिक डेटा की रिकॉर्ड) तो बस इस संग्रह में से कोई भी काम करने या नौकरियों की सामग्री जोड़ने/घटाने के बिना चेकलिस्ट को अपडेट करें)।

मुझे नहीं पता कि यह सहायक होगा, लेकिन यहां यह वैसे भी है।

कोड - checklist.rb (मॉडल)

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 

कोड - checklists_controller.rb (नियंत्रक)

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 

कोड - चेकलिस्ट रूप

<%= 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

आईएमओ, आपको सीधे '_destroy' पैराम में हेरफेर करना चाहिए। मुझे लगता है कि एक कार्यान्वयन विस्तार के रूप में जो खून बहता है ताकि क्लाइंट जावास्क्रिप्ट के माध्यम से काम कर सके। सर्वर पर, 'mark_for_destruction' और 'mark_for_destruction' का उपयोग करें? Http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html देखें –

5

शायद आप जटिल accepts_nested सामान को बाहर निकालना चाहते हैं और आवश्यक सभी चरणों को शामिल करने के लिए कस्टम क्लास या मॉड्यूल बनाना चाहते हैं।

वहाँ के बाद से मारियो मेरे सवाल पर टिप्पणी की और पूछा कि क्या मैं इसे हल इस पोस्ट

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

विशेष रूप से बिंदु 3

+0

मुझे लगता है कि आप सही हैं। मैंने पहले ब्लॉग पोस्ट देखा है, और मुझे लगता है कि यह वास्तव में ऊपर वर्णित रेडटेप मणि को प्रेरित करता है। मैं कहीं और पूछ रहा हूं, और रेडटेप दो बार आया है, तो शायद यही वह जगह है जहां मुझे जाना है। – JoshDoody

संबंधित मुद्दे