2010-06-14 20 views
20

मेरे पास वास्तव में एक सरल रेल एप्लिकेशन है जो उपयोगकर्ताओं को पाठ्यक्रमों के एक सेट पर अपनी उपस्थिति पंजीकृत करने की अनुमति देता है। ActiveRecord मॉडल इस प्रकार हैं:मैं अपने रेल ऐप में दौड़ की स्थिति से कैसे बचूं?

class Course < ActiveRecord::Base 
    has_many :scheduled_runs 
    ... 
end 

class ScheduledRun < ActiveRecord::Base 
    belongs_to :course 
    has_many :attendances 
    has_many :attendees, :through => :attendances 
    ... 
end 

class Attendance < ActiveRecord::Base 
    belongs_to :user 
    belongs_to :scheduled_run, :counter_cache => true 
    ... 
end 

class User < ActiveRecord::Base 
    has_many :attendances 
    has_many :registered_courses, :through => :attendances, :source => :scheduled_run 
end 

एक ScheduledRun उदाहरण उपलब्ध स्थानों की एक सीमित संख्या है, और एक बार सीमा पार होने के कोई और अधिक उपस्थिति स्वीकार किया जा सकता।

def full? 
    attendances_count == capacity 
end 

attendances_count एक काउंटर कैश एक विशेष ScheduledRun रिकार्ड के लिए बनाई गई उपस्थिति संघों की संख्या पकड़े स्तंभ है।

मेरी समस्या यह है कि मैं यह सुनिश्चित करने के लिए सही तरीके से पूरी तरह से नहीं जानता कि दौड़ की स्थिति तब नहीं होती है जब 1 या अधिक लोग एक ही समय में पाठ्यक्रम पर अंतिम उपलब्ध स्थान के लिए पंजीकरण करने का प्रयास करते हैं।

मेरे उपस्थिति नियंत्रक इस तरह दिखता है:

class AttendancesController < ApplicationController 
    before_filter :load_scheduled_run 
    before_filter :load_user, :only => :create 

    def new 
    @user = User.new 
    end 

    def create 
    unless @user.valid? 
     render :action => 'new' 
    end 

    @attendance = @user.attendances.build(:scheduled_run_id => params[:scheduled_run_id]) 

    if @attendance.save 
     flash[:notice] = "Successfully created attendance." 
     redirect_to root_url 
    else 
     render :action => 'new' 
    end 

    end 

    protected 
    def load_scheduled_run 
    @run = ScheduledRun.find(params[:scheduled_run_id]) 
    end 

    def load_user 
    @user = User.create_new_or_load_existing(params[:user]) 
    end 

end 

आप देख सकते हैं, यह खाता है, जहां ScheduledRun उदाहरण पहले से ही क्षमता तक पहुँच गया है पर ध्यान नहीं देता।

किसी भी मदद के लिए इस पर बहुत सराहना की जाएगी।

अद्यतन

मैं कुछ नहीं कर रहा हूँ अगर यह इस मामले में आशावादी ताला प्रदर्शन करने के लिए सही तरीका है, लेकिन यहाँ मैं क्या किया है:

मैं ScheduledRuns मेज पर दो कॉलम जोड़ा

: -

t.integer :attendances_count, :default => 0 
t.integer :lock_version, :default => 0 

मैं भी ScheduledRun मॉडल के लिए एक विधि जोड़ा

def attend(user) 
    attendance = self.attendances.build(:user_id => user.id) 
    attendance.save 
    rescue ActiveRecord::StaleObjectError 
    self.reload! 
    retry unless full? 
    end 

जब उपस्थिति मॉडल सहेजा जाता है, ActiveRecord आगे बढ़ता है और अनुसूचित रून मॉडल पर काउंटर कैश कॉलम अपडेट करता है। यहाँ लॉग उत्पादन दिखाने वाला ऐसा होता है -

ScheduledRun Load (0.2ms) SELECT * FROM `scheduled_runs` WHERE (`scheduled_runs`.`id` = 113338481) ORDER BY date DESC 

Attendance Create (0.2ms) INSERT INTO `attendances` (`created_at`, `scheduled_run_id`, `updated_at`, `user_id`) VALUES('2010-06-15 10:16:43', 113338481, '2010-06-15 10:16:43', 350162832) 

ScheduledRun Update (0.2ms) UPDATE `scheduled_runs` SET `lock_version` = COALESCE(`lock_version`, 0) + 1, `attendances_count` = COALESCE(`attendances_count`, 0) + 1 WHERE (`id` = 113338481) 

बाद में एक अद्यतन ScheduledRun मॉडल से पहले नए उपस्थिति मॉडल सहेजा जाता है करने के लिए होता है, तो इस StaleObjectError अपवाद सक्रिय होना चाहिए। उस बिंदु पर, अगर पूरी तरह से पहले से ही नहीं पहुंच पाई है, तो पूरी चीज फिर से कोशिश की जाती है।

अद्यतन # 2

यहाँ है SheduledRun वस्तु पर अद्यतन में भाग लेने विधि @ kenn की प्रतिक्रिया से इसे जारी रखते हुए:

# creates a new attendee on a course 
def attend(user) 
    ScheduledRun.transaction do 
    begin 
     attendance = self.attendances.build(:user_id => user.id) 
     self.touch # force parent object to update its lock version 
     attendance.save # as child object creation in hm association skips locking mechanism 
    rescue ActiveRecord::StaleObjectError 
     self.reload! 
     retry unless full? 
    end 
    end 
end 
+0

नवीनतम रेल में फिक्स्ड। –

+0

आपको आशावादी लॉकिंग का उपयोग करने की आवश्यकता है। यह स्क्रीनकास्ट आपको दिखाएगा कि यह कैसे करें: [लिंक टेक्स्ट] (http://railscasts.com/episodes/59-optimistic-locking) – rtacconi

+0

आपका क्या मतलब है, डमित्री? – Edward

उत्तर

13

आशावादी लॉकिंग जाने का तरीका है, लेकिन जैसा कि आपने पहले ही देखा होगा, आपका कोड कभी भी ActiveRecord :: StaleObjectError नहीं बढ़ाएगा, क्योंकि बच्चे ऑब्जेक्ट सृजन में है_मनी एसोसिएशन लॉकिंग तंत्र को छोड़ देता है। निम्नलिखित एसक्यूएल पर एक नज़र डालें:

UPDATE `scheduled_runs` SET `lock_version` = COALESCE(`lock_version`, 0) + 1, `attendances_count` = COALESCE(`attendances_count`, 0) + 1 WHERE (`id` = 113338481) 

आप माता पिता वस्तु की विशेषताओं का अद्यतन करते हैं, तो आप आमतौर पर निम्नलिखित एसक्यूएल बजाय देखें:

UPDATE `scheduled_runs` SET `updated_at` = '2010-07-23 10:44:19', `lock_version` = 2 WHERE id = 113338481 AND `lock_version` = 1 

ऊपर बयान दिखाता है कि आशावादी ताला कार्यान्वित किया जाता है : WHERE खंड में lock_version = 1 पर ध्यान दें। जब दौड़ की स्थिति होती है, समवर्ती प्रक्रियाएं इस सटीक क्वेरी को चलाने का प्रयास करती हैं, लेकिन केवल पहला सफल होता है, क्योंकि पहला व्यक्ति परमाणु रूप से लॉक_वर्जन को 2 तक अपडेट करता है, और बाद की प्रक्रिया में विफल हो जाएगी रिकॉर्ड और ActiveRecord :: StaleObjectError को बढ़ाएं, चूंकि एक ही रिकॉर्ड में अब lock_version = 1 नहीं है।

तो, आपके मामले में, एक संभावित समाधान, माता-पिता को छूने के लिए सही से पहले आपके द्वारा बनाए गए/एक बच्चे वस्तु को नष्ट, इसलिए की तरह है:

def attend(user) 
    self.touch # Assuming you have updated_at column 
    attendance = self.attendances.create(:user_id => user.id) 
rescue ActiveRecord::StaleObjectError 
    #...do something... 
end 

यह सख्ती से दौड़ की स्थिति से बचने के लिए मतलब नहीं है, लेकिन व्यावहारिक रूप से यह ज्यादातर मामलों में काम करना चाहिए।

+0

धन्यवाद केन। मुझे एहसास नहीं हुआ कि बाल वस्तु निर्माण ने लॉकिंग तंत्र को छोड़ दिया है। मैंने पूरी चीज को लेन-देन में भी लपेट लिया, बस बच्चे ऑब्जेक्ट निर्माण विफल होने पर मूल ऑब्जेक्ट को अनावश्यक रूप से अपडेट नहीं किया जाता है। – Cathal

0

तुम सिर्फ अगर @run.full? परीक्षण करने के लिए नहीं है?

def create 
    unless @user.valid? || @run.full? 
     render :action => 'new' 
    end 

    # ... 
end 

संपादित

अगर आप की तरह एक मान्यता जोड़ने क्या:

class Attendance < ActiveRecord::Base 
    validate :validates_scheduled_run 

    def scheduled_run 
     errors.add_to_base("Error message") if self.scheduled_run.full? 
    end 
end 

यह @attendance नहीं बचा लेगा अगर जुड़े scheduled_run भरा हुआ है।

मैंने इस कोड का परीक्षण नहीं किया है ... लेकिन मुझे विश्वास है कि यह ठीक है।

+0

यह काम नहीं करेगा। समस्या यह है कि रिकॉर्ड @run का प्रतिनिधित्व पहले से ही किसी अन्य अनुरोध द्वारा अपडेट किया जा सकता है, जिससे डेटाबेस में जो कुछ भी प्रदर्शित होता है, उसे @run असंगत छोड़ दिया जाता है। मेरे ज्ञान के लिए, आशावादी लॉकिंग इस समस्या को हल करने का तरीका है। हालांकि, आप इसे संघों में लागू करने के बारे में कैसे जाते हैं? – Cathal

+0

सही ... मैंने अपना जवाब संपादित कर लिया है:] –