如果這個問題看起來很雜亂,我真的很抱歉;我會盡力使其簡潔。記憶類變量返回不一致的值
我正在構建一個模擬ActiveRecord模型的類,但從名爲Airtable的服務中獲取其數據,而不是數據庫。 Airtable就像Excel和數據庫之間的交叉 - 它可以讓你創建一個數據電子表格,但支持不同表格之間的「外鍵」,因此你可以在表格之間鏈接數據。這對我正在處理的應用程序來說真的很好。
爲了使它具有可擴展性和靈活性,我創建了一個父類AirtableModel
,它定義了在類從它繼承時將被填充的常用方法和屬性。繼承類的名稱將幫助父方法訪問正確的Airtable表中的數據並檢索正確的屬性。相關位下方(未提及的人是不言自明或者是無關緊要的問題):
class AirtableModel
def initialize(hash)
hash.each do |attribute_name, attribute_value|
attribute_value = self.class.first_value_from_arrays_with_singular_key_name(attribute_name, attribute_value)
# ^^^ Airtable always returns references as Arrays. If the relationship is a belongs_to, we pull out the value from the Array.
begin
attribute_name_as_class = attribute_name.to_s.singularize.camelize.constantize
# ^^^ Converts the attribute's name to a class constant. Used to make the generated method retrieve class records instead of ids. If the class doesn't exist, its NameError is caught below.
instance_variable_set("@#{attribute_name}_airtable_ids", attribute_value)
self.class.send(:define_method, attribute_name.to_sym) do
result = attribute_name_as_class.find_all_by_airtable_id(instance_variable_get("@#{attribute_name}_airtable_ids"))
result.length <= 1 ? result.first : result
end
rescue NameError
# Triggered if `attribute_name_as_class` doesn't match an existing class
instance_variable_set("@#{attribute_name}", attribute_value)
self.class.send(:define_method, attribute_name.to_sym) do
instance_variable_get("@#{attribute_name}")
end
end
end
end
# Reaches out to Airtable to get all records for this class's table (the Airtable table matches the class name). Collects the resulting data into an array of Hashes.
# One such hash might look like this:
# {
# 'id' => <unique string ID assigned by Airtable>,
# 'fields' => {
# 'db_id' => <Unique integer ID. I added this to emulate a database record>,
# ...
# }
# }
def self.airtable
@airtable_records ||= AirtableService.records_from_table(table_name: "#{self}s").each.map do |raw|
object_properties = raw['fields']
object_properties['airtable_id'] = raw['id']
object_properties['id'] = object_properties['db_id']
Hash[object_properties.collect { |k, v| [k.snakecase.parameterize.underscore.to_sym, v] }]
# ^^^ Converts parameter name to snake-case symbol, i.e. :db_id
end
end
def self.all
@all_records ||= airtable.map { |b| new(b) }
end
def self.find_by_airtable_id(airtable_id)
objects = all.select { |b| b.airtable_id == airtable_id }
raise "non unique airtable_id found" if objects.size > 1
objects.first
end
def self.find_all_by_airtable_id(airtable_ids)
[airtable_ids].flatten.map { |aid| find_by_airtable_id(aid) }
# ^^^ Accomodates airtable_ids as an Array or a single value
end
def self.first
all.first
end
def self.last
all.last
end
end
如果上面的東西沒有意義,讓我知道,我會很高興更新。
這對於我的絕大多數繼承於AirtableModel
的類都是完美的,但我遇到了一個特定表(FooBar)的問題,它應該像另一個表之間的連接表一樣工作。這將是這個樣子:
[Table Foo] [Table FooBar] [Table Bar]
fooBars <==========---------> foo bar <---------========> fooBars
它們的類定義非常簡單:
class Foo < AirtableModel
end
class FooBar < AirtableModel
end
class Bar < AirtableModel
end
由於上面的構造,我可以撥打電話一樣Foo.first.foo_bars
並取回所有FooBar
實例數組與此相關Foo
。這在控制檯中沒有問題,但我遇到了一個問題,在我的Rails應用程序中嘗試上面的代碼片段。
foo_bars
在單個控制器創建操作中被調用兩次。這發生兩次呼叫self.all
。第一次,我得到預期的結果 - @all_records
等於我在Airtable中的記錄數,並帶有正確的屬性值,包括外鍵關係。但是,第二次輸入方法時,@all_records
的值更改爲空數組。調用foo_bars
的對象沒有更改,並且仍包含用於查找相關聯的實例的正確airtable_ids
。 @airtable_records
- 來自self.airtable
方法的返回值 - 仍然具有相同的值。
我不確定是什麼導致記憶@all_records
變量改變價值。我一直在嘲弄我的頭,用調試器逐步跟蹤函數調用,但我看不到是什麼原因導致值發生改變。任何人都可以提供關於如何進一步調試的建議嗎?我將不勝感激。
我承認我沒有徹底地讀過你的問題(這有點冗長),但有一件事跳出來有點不合常規 - 你正在記憶一個類方法中的實例變量。雖然你可以用ruby做到這一點,但因爲類也是對象,所以這通常不是意圖。相反,我想知道是否打算使用類變量,例如'@@ airtable_records'。注意類變量的@@'而不是'@'作爲實例變量。 –
@ Class實例的@SeanHuber實例變量(自定義類是'Class'類的所有實例)是完全有效的模式。此外,在ruby中使用'@@'類變量會因派生類的奇怪行爲而受到阻礙。 – mudasobwa
夠公平的,我收回我的評論。一個memoized的類級別變量對我來說仍然看起來像代碼味道。如果目標是讓一個對象擁有一些信息,我想你會想用單例方法。 –