2009-10-07 49 views
19

Phương pháp kết hợp đường ray hoạt động như thế nào? Cho phép xem xét ví dụ nàyPhương thức kết hợp đường ray hoạt động như thế nào?

class User < ActiveRecord::Base 
    has_many :articles 
end 

class Article < ActiveRecord::Base 
    belongs_to :user 
end 

Bây giờ tôi có thể làm một cái gì đó giống như

@user = User.find(:first) 
@user.articles 

này fetches tôi điều thuộc về người dùng đó. Càng xa càng tốt.

Bây giờ, tôi có thể tiếp tục và tìm hiểu về các bài viết này với một số điều kiện.

@user.articles.find(:all, :conditions => {:sector_id => 3}) 

Hoặc đơn giản tuyên bố và các hiệp hội phương pháp như

class User < ActiveRecord::Base 
    has_many :articles do 
    def of_sector(sector_id) 
     find(:all, :conditions => {:sector_id => sector_id}) 
    end 
    end 
end 

Và làm

@user.articles.of_sector(3) 

Bây giờ câu hỏi của tôi là, làm thế nào để find công việc này trên các mảng ActiveRecord đối tượng lấy bằng hiệp hội phương pháp? Bởi vì nếu chúng ta thực hiện phương thức cá thể User riêng của chúng tôi được gọi là articles và viết thực hiện riêng của chúng tôi, chúng tôi sẽ cung cấp kết quả chính xác giống như phương pháp kết hợp, tìm trên mảng tìm nạp của ActiveRecord đối tượng sẽ không hoạt động.

Đoán của tôi là các phương pháp liên kết đính kèm các thuộc tính nhất định vào mảng đối tượng đã tìm nạp cho phép truy vấn thêm bằng cách sử dụng find và các phương thức ActiveRecord khác. Chuỗi thực thi mã trong trường hợp này là gì? Làm thế nào tôi có thể xác nhận điều này?

Trả lời

20

Làm thế nào nó thực sự công trình là đối tượng kết hợp là "đối tượng proxy". Lớp học cụ thể là AssociationProxy. Nếu bạn nhìn vào dòng 52 của file đó, bạn sẽ thấy:

instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ } 

Bằng cách này, các phương pháp như class không còn tồn tại trên đối tượng này. Vì vậy, nếu bạn gọi class trên đối tượng này, bạn sẽ bị thiếu phương thức. Vì vậy, có một method_missing thực hiện cho đối tượng proxy đó chuyển tiếp cuộc gọi phương pháp để "mục tiêu":

def method_missing(method, *args) 
    if load_target 
    unless @target.respond_to?(method) 
     message = "undefined method `#{method.to_s}' for \"#{@target}\":#{@target.class.to_s}" 
     raise NoMethodError, message 
    end 

    if block_given? 
     @target.send(method, *args) { |*block_args| yield(*block_args) } 
    else 
     @target.send(method, *args) 
    end 
    end 
end 

Mục tiêu là một mảng, vì vậy khi bạn gọi class trên đối tượng này, nó nói nó là một mảng, nhưng đó chỉ là vì mục tiêu là một mảng, lớp thực sự là một AssociationProxy, nhưng bạn không thể thấy điều đó nữa.

Vì vậy, tất cả các phương pháp bạn thêm, chẳng hạn như of_sector, được thêm vào proxy kết hợp, để chúng được gọi trực tiếp. Các phương thức như []class không được xác định trên proxy kết hợp, do đó chúng được gửi đến đích, là một mảng.

Để giúp bạn xem làm thế nào điều này xảy ra, thêm video này vào dòng 217 của tập tin đó trong bản sao địa phương của bạn của association_proxy.rb:

Rails.logger.info "AssociationProxy forwarding call to `#{method.to_s}' method to \"#{@target}\":#{@target.class.to_s}" 

Nếu bạn không biết nơi tập tin có nghĩa là, lệnh gem which 'active_record/associations/association_proxy' sẽ cho bạn biết. Bây giờ khi bạn gọi class trên một AssociationProxy, bạn sẽ thấy một thông báo tường trình cho bạn biết nó đang gửi đến đích, điều này sẽ làm cho nó rõ ràng hơn những gì đang xảy ra. Đây là tất cả cho Rails 2.3.2 và có thể thay đổi trong các phiên bản khác.

+0

Cái này '/ (^ __ |^nil \? $ |^Gửi $ | proxy_ |^object_id $) /' rất vui nhộn Tôi không sợ regex nữa. – jibiel

0

Khi bạn thực hiện liên kết (has_one, has_many, v.v.), nó sẽ yêu cầu mô hình tự động bao gồm một số phương thức của ActiveRecord. Tuy nhiên, khi bạn quyết định tạo một phương thức cá thể trả về kết hợp, bạn sẽ không thể sử dụng các phương thức đó.

Trình tự là một cái gì đó như thế này

  1. thiết lập articles trong User mô hình, tức là làm một has_many :articles
  2. ActiveRecord tự động bao gồm các phương pháp thuận tiện vào mô hình (ví dụ size, empty?, find, all, first, vv)
  3. thiết lập user trong Article, ví dụ: belongs_to :user
  4. ActiveRecord tự động bao gồm các phương thức tiện lợi vào mô hình (ví dụ:user=, v.v.)

Vì vậy, rõ ràng khi bạn khai báo một liên kết, các phương pháp được thêm tự động bởi ActiveRecord, đó là cái đẹp khi nó xử lý một lượng lớn công việc, cần phải được thực hiện bằng tay theo cách khác =)

bạn có thể đọc thêm về nó ở đây: http://guides.rubyonrails.org/association_basics.html#detailed-association-reference

hy vọng điều này giúp =)

+0

Điều này tôi biết. Nhưng câu hỏi của tôi là, nếu ở tất cả tôi đã thực hiện những gì AR làm cho tôi, làm thế nào tôi sẽ thêm những kích thước, trống rỗng ?, Tìm, tất cả, đầu tiên vv phương pháp để mảng của các đối tượng trả lại. Ngoài ra, tôi muốn trình tự trong đó @ user.articles.find (: tất cả,: điều kiện => {: sector_id => 3}) sẽ được thực hiện. Các điều kiện được áp dụng trước khi tìm nạp các đối tượng liên kết? Hoặc tất cả các đối tượng liên kết được tìm nạp trước và được truy vấn lại bằng cách áp dụng các điều kiện trên tập hợp các đối tượng hạn chế? – Chirantan

+0

Mô hình chính, trong trường hợp của bạn, người dùng, được tìm nạp trước (ngay cả với trường hợp của usign: include). và các truy vấn tiếp theo sử dụng user.articles.find được phát hành sau đó, thêm user_id (vì ar đã biết user_id nào sẽ sử dụng). Bạn có thể thử nó bằng cách nhìn vào log file, bằng cách thực hiện: user.find (1) user.articles.find (1) và kiểm tra những truy vấn sql được cấp =) tôi không hiểu tại sao bạn sẽ muốn thực hiện của riêng bạn mặc dù ... trừ khi bạn đang cố gắng để làm điều tương tự với các khuôn khổ khác và bạn chỉ muốn biết làm thế nào ar nó ... – Staelen

+0

Tôi chỉ muốn biết điều đó. :) – Chirantan

9

Như đã đề cập, các hiệp hội kỷ lục hoạt động tạo ra một buttload số liệu các phương pháp tiện lợi. Chắc chắn, bạn có thể viết các phương pháp của riêng bạn để tìm nạp mọi thứ. Nhưng đó không phải là Rails Way.

Đường ray là đỉnh cao của hai mottos. DRY (Đừng lặp lại bản thân) và "Convention over Configuration". Về cơ bản, bằng cách đặt tên mọi thứ theo cách có ý nghĩa, một số phương pháp mạnh mẽ được cung cấp bởi khung công tác có thể trừu tượng hóa tất cả các mã chung. Mã bạn đặt trong câu hỏi là ví dụ hoàn hảo về thứ gì đó có thể được thay thế bằng một cuộc gọi phương thức duy nhất.

Nơi các phương pháp tiện lợi này thực sự tỏa sáng là những tình huống phức tạp hơn. Loại câu hỏi liên quan đến các mô hình, điều kiện, xác nhận hợp lệ, v.v.

Để trả lời câu hỏi của bạn khi bạn làm một cái gì đó như @user.articles.find(:all, :conditions => ["created_at > ? ", tuesday]), Rails chuẩn bị hai truy vấn SQL và sau đó hợp nhất chúng thành một. khi phiên bản của bạn chỉ trả về danh sách các đối tượng. Các phạm vi được đặt tên cũng thực hiện tương tự, nhưng thường không vượt qua ranh giới mô hình.

Bạn có thể xác thực bằng cách kiểm tra các truy vấn SQL trong development.log khi bạn gọi những điều này trong bảng điều khiển. Vì vậy, hãy cho phép nói về Named Scopes một chút vì chúng cung cấp một ví dụ tuyệt vời về cách các thanh điều khiển SQL, và tôi nghĩ chúng là cách đơn giản hơn để chứng minh những gì đang xảy ra phía sau hậu trường, vì chúng không cần bất kỳ mô hình liên kết để thể hiện.

Phạm vi được đặt tên có thể được sử dụng để thực hiện tìm kiếm tùy chỉnh của mô hình. Họ có thể bị xích lại với nhau hoặc thậm chí được gọi thông qua các hiệp hội. Bạn có thể dễ dàng tạo các công cụ tìm tùy chỉnh trả về các danh sách giống hệt nhau, nhưng sau đó bạn chạy vào cùng các vấn đề được đề cập trong Câu hỏi.

class Article < ActiveRecord::Base 
    belongs_to :user 
    has_many :comments 
    has_many :commentators, :through :comments, :class_name => "user" 
    named_scope :edited_scope, :conditions => {:edited => true} 
    named_scope :recent_scope, lambda do 
    { :conditions => ["updated_at > ? ", DateTime.now - 7.days]} 

    def self.edited_method 
    self.find(:all, :conditions => {:edited => true}) 
    end 

    def self.recent_method 
    self.find(:all, :conditions => ["updated_at > ?", DateTime.now - 7 days]) 
    end 
end 

Article.edited_scope 
=>  # Array of articles that have been flagged as edited. 1 SQL query. 
Article.edited_method 
=>  # Array of Articles that have been flagged as edited. 1 SQL query. 
Array.edited_scope == Array.edited_method 
=> true  # return identical lists. 

Article.recent_scope 
=>  # Array of articles that have been updated in the past 7 days. 
    1 SQL query. 
Article.recent_method 
=>  # Array of Articles that have been updated in the past 7 days. 
    1 SQL query. 
Array.recent_scope == Array.recent_method 
=> true  # return identical lists. 

Đây là nơi mà mọi thứ thay đổi:

Article.edited_scope.recent_scope 
=>  # Array of articles that have both been edited and updated 
    in the past 7 days. 1 SQL query. 
Article.edited_method.recent_method 
=> # no method error recent_scope on Array 

# Can't even mix and match. 
Article.edited_scope.recent_method 
=>  # no method error 
Article.recent_method.edited_scope 
=>  # no method error 

# works even across associations. 
@user.articles.edited.comments 
=>  # Array of comments belonging to Articles that are flagged as 
    edited and belong to @user. 1 SQL query. 

Về cơ bản mỗi phạm vi tên là tạo ra một đoạn SQL. Rails sẽ khéo léo hợp nhất với mọi đoạn SQL khác trong chuỗi để tạo ra một truy vấn duy nhất, chính xác lại những gì bạn muốn. Các phương thức được thêm vào bởi các phương thức kết hợp hoạt động theo cùng một cách. Đó là lý do tại sao họ tích hợp liền mạch với named_scopes.

Lý do kết hợp kết hợp & không hoạt động giống với phương pháp of_sector được xác định trong câu hỏi không hoạt động. edit_methods trả về một mảng, trong đó như edit_scope (cũng như tìm và tất cả các phương thức tiện lợi AR khác được gọi là một phần của một chuỗi), hãy chuyển đoạn SQL của chúng sang hướng tiếp theo trong chuỗi. Nếu đó là lần cuối cùng trong chuỗi nó thực hiện truy vấn. Tương tự, điều này cũng không hoạt động.

@edited = Article.edited_scope 
@edited.recent_scope 

Bạn đã cố gắng sử dụng mã này.Đây là cách thích hợp để làm điều đó:

class User < ActiveRecord::Base 
    has_many :articles do 
    def of_sector(sector_id) 
     find(:all, :conditions => {:sector_id => sector_id}) 
    end 
    end 
end 

Để đạt được chức năng này, bạn muốn làm điều này:

class Articles < ActiveRecord::Base 
    belongs_to :user 
    named_scope :of_sector, lambda do |*sectors| 
    { :conditions => {:sector_id => sectors} } 
    end 
end 

class User < ActiveRecord::Base 
    has_many :articles 
end 

Sau đó, bạn có thể làm những việc như thế này:

@user.articles.of_sector(4) 
=> # articles belonging to @user and sector of 4 
@user.articles.of_sector(5,6) 
=> # articles belonging to @user and either sector 4 or 5 
@user.articles.of_sector([1,2,3,]) 
=> # articles belonging to @user and either sector 1,2, or 3 
+0

Tôi không nghĩ rằng điều này thực sự trả lời câu hỏi. Điều này giải thích cách sử dụng các liên kết và phạm vi được đặt tên, nhưng không giải thích cách chúng thực sự hoạt động. Xem câu trả lời của tôi cho điều đó. – pjb3

+0

Rails không thực sự biết "phạm vi cuối cùng trong chuỗi" là gì. Trong thực tế, không có phạm vi được đặt tên nào thực sự thực thi bất kỳ cuộc gọi SQL nào. Đó là chỉ khi bạn * làm * một cái gì đó với phạm vi (ví dụ .each nếu iterating, hoặc .to_s như 'puts' ngầm định không) rằng cơ sở dữ liệu nó truy vấn. – Gareth

1

Như metioned trước đây, khi thực hiện

@user.articles.class 
=> Array 

gì bạn thực sự nhận được là Array. Đó là vì phương thứC#class không được xác định, như đã đề cập trước đó.

Nhưng làm thế nào để bạn có được lớp thực tế của @ user.articles (mà nên là proxy)?

Object.instance_method(:class).bind(@user.articles).call 
=> ActiveRecord::Associations::CollectionProxy 

Và tại sao bạn nhận được Array ngay từ đầu? Vì phương thứC#class được giao cho CollectionProxy @target instance thông qua phương thức missin, mà thực sự là một mảng. Bạn có thể nhìn đằng sau hậu trường bằng cách làm như sau:

@user.articles.proxy_association 
Các vấn đề liên quan