如何强制rails将oracle Date字段视为DateTime?

时间:2018-03-15 17:53:47

标签: ruby-on-rails ruby oracle activerecord

我的问题

Oracle'DATE'列实际上也存储时间,精度低于'TIMESTAMP'(秒与皮秒)。我需要我的应用程序与此旧架构进行交互,就像Date是DateTime一样。因为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将该字段视为DateTime,但在尝试写入db时会导致异常:

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

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

3 个答案:

答案 0 :(得分: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

答案 1 :(得分:0)

一个老问题,但是我今天只需要自己解决这个问题,所以我想分享一下我的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中被视为日期时间。

答案 2 :(得分:0)

将此代码添加到您的代码中以强制Oracle“ DATE”使用DateTime红宝石类型

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