Rails: инициализация атрибутов, которые зависят друг от друга - PullRequest
1 голос
/ 04 марта 2010

В моей модели ActiveRecord есть следующие классы:

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 создавались на основе списка атрибутов, передаваемых из представления. Я привык к использованию операции build (attribute) для этого. Однако теперь возникает проблема, связанная с тем, что я не могу контролировать порядок, в котором происходит инициализация атрибута. Таким образом, присвоение атрибута value не будет работать, когда связь с атрибутом Property еще не была выполнена, поскольку тип_значения не может быть определен. Как правильно "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" выполняется. Но , значение "value = @temp_value" не вызывает операцию "value = (v)" как ни странно! Так что я действительно застрял.

РЕДАКТИРОВАТЬ: код сборки Я действительно понял, что код для создания объектов 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 ...?

Ответы [ 3 ]

1 голос
/ 05 марта 2010

О, блин ... это безумно просто, теперь, когда я немного озадачился этим. Мне просто нужно переопределить метод initialize (attribute = {}) в классе PropertyValue следующим образом:

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

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

1 голос
/ 04 марта 2010

Один из вариантов - сначала сохранить объекты 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

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

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

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

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

0 голосов
/ 04 марта 2010

Вероятно, вам следует попытаться использовать методы get / set 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...