【问题标题】:Sharded Tables Combined into a Single ActiveRecord Model in Rails在 Rails 中将分片表组合成单个 ActiveRecord 模型
【发布时间】:2012-11-15 04:21:07
【问题描述】:

我是 Ruby on Rails 的新手(可能很快就会很明显),我正在尝试为以下数据场景找出一个模型。我已经阅读了几篇文章并详细搜索了谷歌,但我仍然感到困惑。

我有 5 个具有相同列的不同表,但 value 列具有不同的数据类型。出于各种充分的原因,他们的数据位于 5 个单独的表中,但可以将其视为跨多个表分片的数据。

logbook_strings (user_id, entry_id, field_id, value) 
logbook_booleans (user_id, entry_id, field_id, value)  
logbook_integers (user_id, entry_id, field_id, value)  
logbook_decimals (user_id, entry_id, field_id, value)  
logbook_datetimes (user_id, entry_id, field_id, value)  

下面是数据的样子:

------------------------------------------------
| user_id | entry_id | field_id | value        |
------------------------------------------------
| 1       | alpha1   | date     | 2012-11-14   |
| 1       | alpha1   | duration | 1.2          |
| 1       | alpha1   | remarks  | Nice job.    |
------------------------------------------------
| 1       | alpha2   | date     | 2012-11-13   |
| 1       | alpha2   | duration | 2.7          |
| 1       | alpha2   | remarks  | Bad job.     |
------------------------------------------------

输入 alpha1:
2012-11-14,1.2,干得好。

输入 alpha2:
2012-11-13, 2.7, 糟糕的工作。

等等

我这样做的原因是我可以拥有一个无限灵活的数据库。我可以随时添加新的field_id 来向我的应用程序添加新的字段/功能,而不是进行架构更新以向宽日志表添加另一列。

所以我想知道的是,有没有一种方法可以让我拥有一个可以引用所有 5 个表的 ActiveRecord 模型?

【问题讨论】:

  • 澄清一下,如果存在这样的模型,您希望调用哪些方法?像这样的东西:lb = Logbook.new; lb.int_value = 10; lb.string_value = "foo"?
  • 好问题。在我的应用程序中,我有日志条目,每个条目中有大约 25-30 个值。我没有使用包含大量列的“日志条目”表(以及要管理的粗糙架构),而是使用上述entry_idfield_id,如下所示:
  • 不幸的是,它切断了代码。也许把它放在 pastebin 或 github gist 中并包含它的链接?或者用它更新您的问题也可以。
  • 查看此链接(比摆弄 SO 的评论框更容易):d.pr/n/Mxc8
  • 所以要回答你的问题,是的,我会像你推荐的那样查询数据库并将数据插入其中。

标签: ruby-on-rails activerecord


【解决方案1】:

在花了几分钟试图将它硬塞进一个 ActiveRecord 类之后,我认为将 ActiveRecord 用于这样的事情并不是一个好主意。我看到了几个选项:

  • 滚动您自己的模型。这种方法的极端缺点是您失去了所有 ActiveRecord 的许多不错的功能。但如果您的数据相对简单(不是很多关联等),那么这可能是一个可行的选择。
  • 重组您的数据。如果此架构/数据是预先存在的,或者出于某种原因必须与移动应用程序的架构匹配,那么这可能不是一个选项。但是,如果您刚开始,Rails 的迁移可以让您随心所欲地添加/删除列非常容易且非常安全,因此我可能会考虑使用更传统的方法。虽然这看起来并不理想,但为了获得 ActiveRecord 的诸多好处,需要认真考虑。

如果您必须保留架构,则为每个日志表创建单独的模型可能是您的最佳选择。

# Migrations
create_table :logbook do |t|
  # Default fields, nothing special
end

create_table :logbook_integers do |t|
  t.integer :logbook_id  # You'd probably want to index this as well
  t.string :name
  t.integer :value
end

create_table :logbook_strings do |t|
  t.integer :logbook_id   # You'd probably want to index this as well
  t.string :name
  t.string :value
end

# etc...

# Models
class Logbook < ActiveRecord::Base
  has_many :logbook_integers
  has_many :logbook_strings
  # etc...

  def remarks
    self.logbook_strings.find_by_name("remarks").value
  end

  def remarks= newValue
    remark = self.logbook_strings.find_or_create_by_name("remarks")
    remark.value = newValue
    remark.save
  end

  # etc...
end

class LogbookInteger < ActiveRecord::Base
  belongs_to :logbook
end

class LogbookString < ActiveRecord::Base
  belongs_to :logbook
end

# etc...

# Usage

logbook = Logbook.new
logbook.remarks = "Hi"
logbook.duration = 2

logbook.remarks         # => Hi
logbook.duration        # => 2

如果您可以稍微更改架构,这里有一个选项:

您可以使用serialize 类方法described here(cmd+f 表示“序列化”)来存储您的条目,这样您就无需拥有许多模型,而只需两个:LogbookLogbookField。它可能看起来像这样:

# Migration for logbook_fields
create_table :logbook_fields do |t|
  t.string :name
  t.string :value
end

# Models
class Logbook
  has_many :logbook_fields

  def self.build_with_default_fields
    self.logbook_fields.create name: "date"
    self.logbook_fields.create name: "duration"
    # etc...
  end

  # You could probably do some cool Ruby metaprogramming to create all these
  # accessors/setters for you, btw.
  def date
    self.logbook_fields.find_by_name "date"
  end

  def date= newValue
    field = self.logbook_fields.find_by_name "date"
    field.value = newValue
    field.save
  end


  def duration
    self.logbook_fields.find_by_name "duration"
  end

  def duration= newValue
    field = self.logbook_fields.find_by_name "duration"
    field.value = newValue
    field.save
  end

  # etc...

end

class LogbookField
  serialize :value

  belongs_to :logbook
end

# Usage

logbook = Logbook.build_with_default_fields
logbook.date = DateTime.now
logbook.duration = 2.7

类似的东西。这样,您就可以保留大部分 ActiveRecord 的细节,同时仍然保持架构设计的一些“无限性”。但是,使用迁移在单个表上添加/删除列可能会比这更容易。同样,这取决于您是否可以灵活地使用架构。希望这会有所帮助。

【讨论】:

  • 我对此深思熟虑,并决定最终简化我的数据库架构会变得更简单、更容易。我将使用一个 logbook 表,并且我的应用程序中的每个“属性”都有一个列。迁移确实使这变得容易,尽管我必须在 iOS 设备上处理 SQLite 上的架构更改,但这应该不会太糟糕。非常感谢您的帮助!
  • 没问题!不过,我不希望任何人手动更改 SQLite 架构。如果您以某种方式使用 Rails 作为 JSON Web 服务或其他东西来同步应用程序,您可以尝试保留 iOS 模式并想出一种方法将 JSON 序列化为 Web 服务器和 iOS 设备都可以理解的格式。这样,您可以在两个设备上保留不同的架构。不确定这是否是您想要做的,但只是一个想法。
【解决方案2】:

我认为您可能应该有一个带有类型列的表。

例如:

logbook(user_id, entry_id, field_id, value, value_type)

值类型将是

strings

booleans  

integers 

decimals 

datetimes

例如

-----------------------------------------------------------
| user_id | entry_id | field_id | value        |value_type |
-----------------------------------------------------------
| 1       | alpha1   | date     | 2012-11-14   | datetime 
| 1       | alpha1   | duration | 1.2          | decimal
| 1       | alpha1   | remarks  | Nice job.    | string

所以基本上值列将是字符串,从模型中你可以决定你想要的值类型,你的模型将是

class Logbook < ActiveRecord::Base

   #sample method
   #just to give an idea how you could use the same value
   #with different times
   def multiple_duration_by_two
     self.value * 2  if self.value_type == "decimal"
   end

end

但是,根据您的要求,此实施可能需要 tweeks,但我想您明白了

HTH

【讨论】:

  • 这是一个有趣的想法——我曾想过,但我会在一些查询中进行大量类型转换。不过,我会考虑一下。
  • 很酷,但是如果您使用 ActiveRecord 连接插入普通查询,我想您的生活会更轻松:),事实上,如果您使用这种方法或其他方法,我建议您使用 ActiveRecord 连接和与普通 sql 查询之间的关系,因为稍后您可以利用全部功能或 AR,HTH
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-02-26
  • 2014-11-12
  • 1970-01-01
  • 1970-01-01
  • 2018-11-01
  • 1970-01-01
  • 2018-08-28
相关资源
最近更新 更多