2012-04-05 25 views
6

Vì vậy, nghĩ rằng tôi đã làm việc này đêm qua, canve tuyên thệ nó. Bây giờ nó không có tác dụng, và tôi hình dung thời gian để yêu cầu giúp đỡ.ruby ​​trên đường ray thuộc tính động lĩnh vực từ DB sử dụng các vấn đề method_missing

Im lĩnh vực năng động quyết định trong cơ sở dữ liệu, phong cách EAV bán, và cho phép chỉ nêu ngay bây giờ tôi không quan tâm để nghe ý kiến ​​của bạn về việc liệu EAV là một ý tưởng tốt hay không :)

Anyways Im làm nó một chút khác với Ive thực hiện trong quá khứ, về cơ bản khi một thuộc tính (hoặc field) được thêm vào, tôi tạo một cột thêm vào một bảng thuộc tính cụ thể di chuyển và chạy nó (hoặc loại bỏ nó) - BẤT KWAY, bởi vì có một lớp thể loại ngồi ở giữa là mối quan hệ trực tiếp nơi tất cả các thuộc tính được xác định, tôi không thể sử dụng tên thuộc tính thực tế làm tên cột, vì thuộc tính là danh mục cụ thể.

Vì vậy, nếu nó giúp bạn hình dung

Entity 
    belongs_to :category 

    Category 
    has_many :entities 

    EntityAttribute 
    belongs_to :category 

    EntityAttributeValue 
    belongs_to :entity_attribute 
    belongs_to :entity   

Và EAV bảng kéo dài theo chiều ngang như các thuộc tính mới được tạo ra, với các cột dán nhãn attribute_1 attribute_2, có chứa các giá trị cho rằng thực thể cụ thể.

Anyways - Tôi cố gắng để thực hiện các phương pháp năng động trên mô hình tổ chức nào, vì vậy tôi có thể gọi @ entity.actual_attribute_name, chứ không phải là @ entity.entity_attribute_value.field_5

Đây là mã tôi nghĩ đang làm việc - -

def method_missing(method, *args) 

     return if self.project_category.blank? 

     puts "Sorry, I don't have #{method}, let me try to find a dynamic one." 
     puts "let me try to find a dynamic one" 

     keys = self.project_category.dynamic_fields.collect {|o| o.name.to_sym } 

     if keys.include?(method) 
     field = self.project_category.dynamic_fields.select { |field| field.name.to_sym == method.to_sym && field.project_category.id == self.project_category.id }.first 
     fields = self.project_category.dynamic_field_values.select {|field| field.name.to_sym == method } 
     self.project_category_field_value.send("field_#{field.id}".to_sym, *args) 
     end 

    end 

sau đó, ngày hôm nay khi tôi quay trở lại mã, tôi nhận ra mặc dù tôi có thể thiết lập các thuộc tính trong đường ray giao diện điều khiển, và nó sẽ trả lại trường đã chính xác, khi tôi lưu bản ghi, các EntityAttributeValue đã không được cập nhật (được đại diện như self.project_category_field_value, ở trên.)

Vì vậy, sau khi nhìn sâu hơn, có vẻ như tôi chỉ cần thêm một lời gọi before_update hoặc before_save để lưu thuộc tính theo cách thủ công và đó là nơi tôi nhận thấy, trong hàm gọi lại, nó sẽ chạy gọi lại method_missing, như thể đối tượng được nhân bản (và đối tượng mới là bản sao của đối tượng gốc), hoặc một cái gì đó, Im không hoàn toàn chắc chắn. Nhưng tại một nơi nào đó trong quá trình lưu hoặc trước đây, thuộc tính của tôi biến mất thành lãng quên. Vì vậy, tôi đoán tôi đã trả lời một nửa câu hỏi của chính mình sau khi gõ nó ra, tôi cần phải đặt một biến cá thể và kiểm tra xem nó có tồn tại khi bắt đầu phương thức method_missing của tôi (đúng không?) Có lẽ đó không phải là những gì xảy ra tôi không biết, nhưng Im cũng yêu cầu nếu có một cách tốt hơn để làm những gì tôi đang cố gắng để làm. Và nếu sử dụng method_missing là một ý tưởng tồi, hãy giải thích tại sao, khi đi qua các bài viết liên quan đến phương pháp, tôi nghe thấy một số người phá hỏng nó, nhưng không một trong số những người đó băn khoăn đưa ra lời giải thích hợp lý. .

Xin cảm ơn trước.

Trả lời

2

Đó là một số chương trình cực kỳ dữ dội đang diễn ra trong bộ phận method_missing. Những gì bạn nên có là một cái gì đó giống như thế này:

def method_missing(name, *args) 
    if (method_name = dynamic_attribute_method_for(name)) 
    method_name.send(*args) 
    else 
    super 
    end 
end 

Sau đó bạn có thể thử và chia nhỏ thành hai phần. Đầu tiên là tạo một phương thức quyết định xem nó có thể xử lý một cuộc gọi với một tên cụ thể không, ở đây dynamic_attribute_method_for và phương thức thứ hai là phương thức thực tế được đề cập.Công việc trước đây là đảm bảo công việc thứ hai hoạt động trước thời gian được gọi, có thể sử dụng define_method để tránh phải xem lại tất cả điều này lần sau khi bạn truy cập cùng tên phương thức.

Đó phương pháp có thể trông như thế này:

def dynamic_attribute_method_for(name) 
    dynamic_attributes = ... 

    type = :reader 

    attribute_name = name.to_s.sub(/=$/) do 
    type = :writer 
    '' 
    end 

    unless (dynamic_attributes.include?(attribute_name)) 
    return 
    end 

    case (type) 
    when :writer 
    define_method(name) do |value| 
     # Whatever you need 
    end 
    else 
    define_method(name) do 
     # Whatever you need 
    end 
    end 

    name 
end 

Tôi không thể nói gì đang xảy ra trong phương pháp của bạn như là cấu trúc không rõ ràng và có vẻ như phụ thuộc nhiều vào bối cảnh của ứng dụng của bạn.

Từ quan điểm thiết kế, bạn có thể thấy dễ dàng hơn khi tạo lớp bao bọc đặc biệt nhằm gói gọn tất cả chức năng này. Thay vì gọi object.attribute_name bạn muốn gọi object.dynamic_attributes.attribute_name nơi trong trường hợp này dynamic_attributes được tạo ra theo yêu cầu:

def dynamic_attributes 
    @dynamic_attributes ||= DynamicAccessor.new(self) 
end 

Khi đối tượng được khởi tạo nó sẽ sẵn cấu hình riêng của mình với bất cứ phương pháp được yêu cầu và bạn sẽ không phải đối phó với phương pháp này thiếu nội dung.

+0

cảm ơn vì đã trả lời, tôi không thể làm cho nó hoạt động một mình thông qua mã ở trên nhưng tôi đã sử dụng một số trong việc tái cấu trúc để tôi chấp nhận câu trả lời – thrice801

0

Đối với bất cứ ai khác cố gắng để làm cùng một loại điều nhưng gặp vấn đề, những vấn đề/giải pháp như xa như tôi có thể hình dung ra là:

1) Mặc dù tôi nghĩ rằng đoạn mã sau sẽ làm việc:

self.project_category_field_value.send("field_#{field.id}".to_sym, *args) 

Điều đó sẽ trả về một phiên bản mới của mô hình liên quan mỗi lần, đó là lý do tại sao nó bị mất.

2) Cần lưu thủ công đối tượng liên quan vì mô hình liên quan sẽ không được lưu. Tôi đã kết thúc việc đặt cờ trên mô hình và thêm gọi lại để lưu mô hình có liên quan nếu cờ tồn tại, ví dụ:

case(type) 

when :writer 
    self.update_dynamic_attributes=(true) 
    etc...... 

và sau đó gọi lại,

before_update :update_dynamic_attributes, :if => :update_dynamic_attributes? 

    def update_dynamic_attributes? 
    instance_variable_get("@update_dynamic_attributes") 
    end 

    def update_dynamic_attributes=(val) 
    instance_variable_set("@update_dynamic_attributes",val) 
    end 

    def update_dynamic_attributes 
    self.project_category_field_value.save 
    end 

3) Trở lại vị trí số 1, vấn đề lớn nhất là trường hợp đối tượng mới được được trả lại mọi lúc. Tôi đã thử sử dụng phương thức define_method trước khi đặt câu hỏi này, nhưng nó không làm việc cho tôi, và điều đó kết thúc là những gì tôi cần làm để làm cho nó hoạt động. - Các giải pháp làm tôi cảm thấy khá ngu ngốc, nhưng Im chắc chắn những người khác sẽ chạy vào nó như là tốt, vì vậy, hãy chắc chắn rằng nếu bạn đang sử dụng define_method trực tiếp trong lớp ghi lại hoạt động, bạn gọi

self.class.send(:define_method, name) 

thay vì

self.send(:define_method, name) 

hoặc bạn sẽ :(khi nó không worky

3

bạn có thể nhìn vào bài thuyết trình của tôi, nơi tôi đã mô tả làm thế nào để ủy thác phương pháp để mô hình liên EAV with ActiveRecord

Ví dụ: chúng tôi sử dụng STI cho các mẫu Sản phẩm của chúng tôi và chúng tôi đã liên kết các mô hình Thuộc tính cho chúng.

Lúc đầu chúng ta tạo ra các trừu tượng Thuộc tính mô hình

class Attribute < ActiveRecord::Base 
    self.abstract_class = true 
    attr_accessible :name, :value 
    belongs_to :entity, polymorphic: true, touch: true, autosave: true 
end 

Sau đó, tất cả các mô hình thuộc tính của chúng tôi được thừa kế từ lớp này.

class IntegerAttribute < Attribute 
end 

class StringAttribute < Attribute 
end 

Bây giờ chúng ta cần phải mô tả cơ sở lớp sản phẩm

class Product < ActiveRecord::Base 
    %w(string integer float boolean).each do |type| 
    has_many :"#{type}_attributes", as: :entity, autosave: true, dependent: :delete_all 
    end 

    def eav_attr_model(name, type) 
    attributes = send("#{type}_attributes") 
    attributes.detect { |attr| attr.name == name } || attributes.build(name: name) 
    end 

    def self.eav(name, type) 
    attr_accessor name 

    attribute_method_matchers.each do |matcher| 
     class_eval <<-EOS, __FILE__, __LINE__ + 1 
     def #{matcher.method_name(name)}(*args) 
      eav_attr_model('#{name}', '#{type}').send :#{matcher.method_name('value')}, *args 
     end 
     EOS 
    end 
    end 
end 

Vì vậy, chúng tôi đã thêm các phương pháp #eav_attr_model mà là một Proxy phương pháp để mô hình có liên quan của chúng tôi và .eav phương pháp mà tạo ra các phương pháp thuộc tính .

Đó là tất cả. Bây giờ chúng ta có thể tạo ra các mô hình sản phẩm được kế thừa từ Sản phẩm lớp.

class SimpleProduct < Product 
    attr_accessible :name 

    eav :code, :string 
    eav :price, :float 
    eav :quantity, :integer 
    eav :active, :boolean 
end 

Cách sử dụng:

SimpleProduct.create(code: '#1', price: 2.75, quantity: 5, active: true) 
product = SimpleProduct.find(1) 
product.code  # "#1" 
product.price # 2.75 
product.quantity # 5 
product.active? # true 

product.price_changed? # false 
product.price = 3.50 
product.code_changed? # true 
product.code_was  # 2.75 

nếu bạn cần aa giải pháp phức tạp hơn cho phép để tạo ra các thuộc tính trong thời gian chạy hoặc sử dụng phương pháp truy vấn để có được dữ liệu bạn có thể nhìn vào viên ngọc của tôi hydra_attribute mà thực hiện các EAV cho các mô hình active_record.

+0

Cảm ơn bạn đã chia sẻ. Tôi chỉ cố gắng để có được một mô hình EAV lên, và điều này đã được hữu ích. –

Các vấn đề liên quan