【问题标题】:How can I force rails to treat an oracle Date field as DateTime?如何强制 rails 将 oracle Date 字段视为 DateTime?
【发布时间】:2018-03-15 17:53:47
【问题描述】:

我的问题

Oracle 的“DATE”列实际上也存储时间,只是精度低于“TIMESTAMP”(秒与皮秒)。我需要我的应用程序与这个遗留模式进行交互,就好像日期是日期时间一样。因为 rails 将此字段视为日期,所以它会截断时间。

示例:

2.4.1 :003 > m.send_after_ts = Time.now
 => 2018-03-15 11:45:50 -0600
2.4.1 :004 > m.send_after_ts
 => Thu, 15 Mar 2018

配置数据:

#columns的结果:

#<ActiveRecord::ConnectionAdapters::OracleEnhancedColumn:0x00000005501658
  @collation=nil,
  @comment=nil,
  @default=nil,
  @default_function=nil,
  @name="send_after_ts",
  @null=true,
  @object_type=false,
  @returning_id=false,
  @sql_type_metadata=
   #<ActiveRecord::ConnectionAdapters::SqlTypeMetadata:0x00000005501720
    @limit=nil,
    @precision=nil,
    @scale=nil,
    @sql_type="DATE",
    @type=:date>,
  @table_name="mail",
  @virtual=false>,

版本:

ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-darwin17]
Rails 5.1.5
activerecord (5.1.5)
activerecord-oracle_enhanced-adapter (1.8.2)
ruby-oci8 (2.2.5.1)

我认为必须有一个映射来管理这种关系?如何让 Rails 将此字段转换为时间戳?

更新

在我的模型中添加attribute :send_after_ts, :datetime 允许rails 将该字段视为日期时间,但在尝试写入数据库时​​会导致异常:

SQL (4.4ms)  INSERT INTO "MAIL" ("SEND_AFTER_TS", "ID") VALUES (:a1, :a2)  [["send_after_ts", "2018-03-15 12:58:02.635810"], ["id", 6778767]]
ActiveRecord::StatementInvalid: OCIError: ORA-01830: date format picture ends before converting entire input string: INSERT INTO "MAIL" ("SEND_AFTER_TS", "ID") VALUES (:a1, :a2)
    from (irb):3

我认为这是由额外的精度(小数秒)引起的,但我没有看到将其定义为属性设置的一部分的方法。

我现在可以通过将此字段写为字符串来解决这个问题,例如:

2.4.1 :013 > m.send_after_ts = Time.now.to_s
 => "Mar 15, 2018 12:48"
2.4.1 :014 > m.save
 => true

我仍在寻找真正的解决方案。

【问题讨论】:

  • 您查看过“属性 API”吗? api.rubyonrails.org/classes/ActiveRecord/Attributes/…
  • 不,我在搜索中错过了这一点。 attribute :send_after_ts, :datetime 成功了。如果您愿意将其写为答案,我很乐意接受。谢谢!
  • 我可能说得太早了。它没有将时间戳写入数据库。

标签: ruby-on-rails ruby oracle activerecord


【解决方案1】:

您可以使用虚拟属性模式作为您的界面:

def send_after_ts_datetime
  send_after_ts.to_datetime
end

def send_after_ts_datetime=(datetime)
  self.send_after_ts = datetime.to_s
end

现在您将使用_datetime 方法读取和写入属性,但您的数据将存储在原始send_after_ts 列中。

>> m.send_after_ts_datetime = Time.now
#> "2018-03-15 15:15:49 -0600"

>> m.send_after_ts_datetime
#> Thu, 15 Mar 2018 15:15:49 -0600

【讨论】:

    【解决方案2】:

    老问题,但我今天必须自己处理这个问题,所以我想我会分享我的 hacky 解决方案。

    OracleEnhancedAdapter 扩展 ActiveRecord::ConnectionAdapters::AbstractAdapter。在其初始化期间,它调用它的initialize_type_map 方法来映射这些类型。 OracleEnhancedAdapter 包含对此的覆盖,它反过来调用AbstractAdapter 中的超类方法。在这里,它将不需要的日期类型映射到 oracle DATE 类型:

    register_class_with_precision m, %r(date)i,      Type::Date
    

    在我的“修复”中,我在 rails 中创建了一个初始化器来覆盖这个方法:

    ActiveSupport.on_load(:active_record) do
      ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
        # ... some other method customization
        def initialize_type_map(m)
          # other code from the AbstractAdapter implementation
    
          # actually make the mapping to the DateTime that you really want
          register_class_with_precision m, %r(date)i,      ActiveRecord::Type::DateTime
    
          # ... code from the OracleEnhancedAdapter implementation
        end
      end
    end
    

    结果是 oracle DATE 类型现在在 Rails 中被视为日期时间。

    【讨论】:

      【解决方案3】:

      将此添加到您的代码中以强制 Oracle "DATE" 使用 DateTime ruby​​ 类型

      require 'active_record/connection_adapters/oracle_enhanced_adapter'
      
      module ActiveRecord
        module ConnectionAdapters
          class OracleEnhancedAdapter
            alias :old_initialize_type_map :initialize_type_map
            def initialize_type_map(m = type_map)
              old_initialize_type_map(m)
              m.register_type "DATE", Type::DateTime.new
            end
          end
        end
      end
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多