如果这个问题看起来很混乱,我非常抱歉;我会尽我最大的努力使它变得简洁。
我正在构建一个模拟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的调用,并获得与此Foo相关的所有FooBar实例的数组。这在控制台中没有问题,但我在Rails应用程序中尝试上面的代码片段时遇到了问题。
foo_bars在单个控制器创建操作中被调用两次。这恰好调用了self.all两次。第一次,我得到了预期的结果- @all_records等于我在Airtable中拥有的记录数,以及适当的属性值,包括外键关系。但是,第二次输入该方法时,@all_records的值将更改为空数组。调用foo_bars的对象没有改变,仍然包含用于查找相关FooBar实例的正确airtable_ids。self.airtable方法的返回值@airtable_records也具有相同的值。
我不确定是什么原因导致memoized @all_records变量更改值。我一直在用调试器一步一步地跟踪函数调用,但我看不出是什么导致了值的变化。有没有人能提供一些关于如何进一步调试的建议?我将不胜感激。
发布于 2017-09-01 02:11:29
事实证明答案真的很愚蠢。
all正在返回对象数组。在类的其他地方,我们有这个方法:
def self.where(filter = {})
filtered_objects = all
filter.each do |filter_property, filter_value|
# filter_value = filter_value.airtable_id if filter_value.respond_to?(:airtable_id)
filtered_objects.select! do |object|
object_value = object.send(filter_property)
match_check = lambda do |value|
if object_value.is_a?(Array)
object_value.include?(value)
else
object_value == value
end
end
filter_value.is_a?(Array) ? filter_value.any? { |v| match_check.call(v) } : match_check.call(filter_value)
end
end
filtered_objects
end如果filtered_objects == all,并且我们在filtered_objects上调用select!,会发生什么?
是啊。它直接修改了对象引用。让all返回一个.dup'd版本的数组解决了这个问题。
https://stackoverflow.com/questions/45972432
复制相似问题