2010-03-04 3 views
1

У меня есть следующие классы в моей ActiveRecord модели:Rails: Инициализация атрибутов, которые зависят друг от друга

def Property < ActiveRecord::Base 
    # attribute: value_type (can hold values like :integer, :string) 
end 

def PropertyValue < ActiveRecord::Base 
    belongs_to property 
    # attribute: string_value 
    # attribute: integer_value 
end 
объект

PropertyValue предназначен для хранения только строковое значение или целое значение, в зависимости от типа , указанный в атрибуте value_type связанного объекта Property. Очевидно, что мы не должны беспокоить пользователя класса PropertyValue этим базовым механизмом string_value/integer_value. Поэтому я хотел бы использовать виртуальный атрибут «значение» на PropertyValue, что делает что-то вроде этого:

def value 
    unless property.nil? || property.value_type.nil? 
    read_attribute((property.value_type.to_s + "_value").to_sym) 
    end 
end 

def value=(v) 
    unless property.nil? || property.value_type.nil? 
    write_attribute((property.value_type.to_s + "_value").to_sym, v) 
    end 
end 

Я хочу предложить пользователю вид заполнить кучу значений свойств, а когда вид , я хотел бы, чтобы объекты PropertyValue создавались на основе списка атрибутов, передаваемых из представления. Для этого я использую операцию сборки (атрибутов). Однако теперь возникает проблема, что я не контролирую порядок, в котором происходит инициализация атрибута. Таким образом, присвоение атрибута value не будет работать, если ассоциация с атрибутом Property еще не была сделана, поскольку параметр value_type не может быть определен. Каков правильный способ «Rails» справиться с этим?

Кстати, в качестве обходного пути я попытался следующие:

def value=(v) 
    if property.nil? || property.value_type.nil? 
    @temp_value = v 
    else 
    write_attribute((property.value_type.to_s + "_value").to_sym, v) 
    end 
end 

def after_initialize 
    value = @temp_value 
end 

Помимо того, что я думаю, что это довольно уродливое решение, оно фактически не работает с операцией «строить». Значение @temp_value устанавливается в операции «value = (v)». Кроме того, «after_initialize» выполняется. Но «значение = @temp_value» не вызывает операцию «value = (v)» как ни странно! Поэтому я действительно застрял.

EDIT: код сборки Я действительно понял, что код для создания объектов Property был бы удобен. Я делаю это из класса Product, у которого есть ассоциация has_many с Property. Код будет выглядеть следующим образом:

def property_value_attributes=(property_value_attributes) 
    property_value_attributes.each do |attributes| 
    product_property_values.build(attributes) 
    end 
end 

В то же время я понял, что я сделал неправильно в операции after_initialize; следует читать:

def after_initialize 
    @value = @temp_value 
end 

Другая проблема состоит в том, что имущество объединения на новом объекте property_value никогда не будет установлен, пока фактическая не сохранить() имеет место, что после «after_initialize». Я получил это для работы, добавив value_type соответствующего объекта свойства в представление и затем передал его через атрибуты, установленные после сообщения. Таким образом, мне не нужно создавать экземпляр объекта Property только для получения значения value_type. Недостаток: мне нужен избыточный аксессуар «value_type» в классе PropertyValue.

Так оно работает, но меня все еще очень интересует, если есть более чистый способ сделать это. Еще один способ - убедиться, что объект свойства сначала привязан к новому PropertyValue, прежде чем инициализировать его другими атрибутами, но затем механизм просочится в «клиентский объект», который также не слишком чист.

Я ожидал бы некоторого способа переопределить функциональность инициализатора таким образом, чтобы я мог повлиять на порядок присвоения атрибутов. Что-то очень распространенное в таких языках, как C# или Java. Но в Rails ...?

+0

Можете ли вы разместить свой код для создания PropertyValue? –

ответ

1

Oh jeeezzzz ... это безумно просто, теперь, когда я немного озадачил его, мне просто нужно переопределить «i nitialize (атрибуты = {})»метод в классе PropertyValue так:

def initialize(attributes = {}) 
    property = Property.find(attributes[:property_id]) unless attributes[:property_id].blank? 
    super(attributes) 
end 

Теперь я всегда уверен, что ассоциация свойство заполняется до того, как другие атрибуты устанавливаются. Я просто не понял достаточно скоро, что операции Rails «build (attributes = {})» и «create (attributes = {}) в конечном итоге сводятся к« new (attributes = {}) ».

+0

Ницца :) Жаль, что я не смог бы поддержать вас больше! Очень удобный наконечник. – nfm

+0

Будьте предупреждены о том, что метод initialize() может быть обойден стороной. http://blog.dalethatcher.com/2008/03/rails-dont-override-initialize-on.html – lulalala

1

Один из вариантов - сначала сохранить объекты Property, а затем добавить объекты PropertyValue.Если вам нужно, вы можете обернуть все это в транзакции, чтобы убедиться, что свойства откатываются, если их соответствующие PropertyValues ​​не могут быть сохранены.

Я не знаю, что ваши собранные данные из формы выглядит, но при условии, что выглядит следующим образом:

@to_create = { :integer => 3, :string => "hello", :string => "world" } 

Вы могли бы сделать что-то вроде этого:

Property.transaction do 
    @to_create.keys.each do |key| 
    p = Properties.create(:value_type => key.to_s) 
    p.save 
    pval = p.property_value.build(:value => @to_create[key]) 
    pval.save 
    end 
end 

Таким образом, вам не нужно беспокоиться о проверке nil для Property или Property.value_type.

Как вы заметили, вы уверены, что вам нужно делать все это в первую очередь? Большинство проектов баз данных, которые я видел, которые имеют такую ​​общую метаинформацию, в конечном итоге очень не масштабируемы и почти всегда являются неправильным решением проблемы. Для получения относительно простого набора информации потребуется много соединений.

Предположим, что у вас есть родительский класс Foo, который содержит пары property/value. Если Foo имеет десять свойств, для этого требуется 20 объединений. Это много издержек БД.

Если вам действительно не нужно запускать SQL-запросы против PropertyValues ​​(например, «получить все Foos, у которых есть свойство« bar »), вы, вероятно, могли бы упростить это, просто добавив атрибут« свойства »в Foo, затем сериализуя . ваш хэш свойства и положить его в этой области Это позволит упростить код, структуры базы данных, и ускорить работу приложения, а также

+0

Решение по-прежнему кажется мне слишком сложным, но я понимаю, что это может быть из-за отсутствия более подробного описания процесса сборки. Что касается количества SQL-запросов: я знаю об этой опасности, но на данный момент это выглядит как наиболее подходящее решение в моем случае. Прежде всего, потому что класс Property содержит несколько атрибутов, чем те, которые я упоминал. Во-вторых, я намерен запрашивать значения. +1 за усилия, которые вы вложили в свой ответ! –

0

Вероятно, вы должны попробовать использовать ActiveRecord получить методы/комплект, то есть:.

def value 
    send("#{property.value_type}_value") unless property || property.value_type  
end 

def value=(v) 
    send("#{property.value_type}_value=", value) unless property || property.value_type 
end 
+0

Как это решить проблему свойства.value_type не заполняется, когда значение необходимо установить? –