Skip to content
This repository
Browse code

Allow precision option for postgresql datetimes

This patch addresses the difficulty of retrieving datetime fields. By default, the database holds a higher precision than the time as a String.

This issue is discussed at length at the following links:
- [#3519](#3519)
- [#3520](#3520)

Also, kudos to @mattscilipoti
  • Loading branch information...
commit 6657ec9a0a05c1b3ddcb7aa679ebfdb7e0bc4959 1 parent a232831
Tony Schneider authored
12  activerecord/CHANGELOG.md
Source Rendered
... ...
@@ -1,5 +1,17 @@
1 1
 ## Rails 4.0.0 (unreleased) ##
2 2
 
  3
+*   Added support for specifying the precision of a timestamp in the postgresql
  4
+    adapter. So, instead of having to incorrectly specify the precision using the
  5
+    `:limit` option, you may use `:precision`, as intended. For example, in a migration:
  6
+
  7
+        def change
  8
+          create_table :foobars do |t|
  9
+            t.timestamps :precision => 0
  10
+          end
  11
+        end
  12
+
  13
+    *Tony Schneider*
  14
+
3 15
 *   Allow ActiveRecord::Relation#pluck to accept multiple columns. Returns an
4 16
     array of arrays containing the typecasted values:
5 17
 
10  activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -187,6 +187,7 @@ def extract_limit(sql_type)
187 187
         case sql_type
188 188
         when /^bigint/i;    8
189 189
         when /^smallint/i;  2
  190
+        when /^timestamp/i; nil
190 191
         else super
191 192
         end
192 193
       end
@@ -201,6 +202,8 @@ def extract_scale(sql_type)
201 202
       def extract_precision(sql_type)
202 203
         if sql_type == 'money'
203 204
           self.class.money_precision
  205
+        elsif sql_type =~ /timestamp/i
  206
+          $1.to_i if sql_type =~ /\((\d+)\)/
204 207
         else
205 208
           super
206 209
         end
@@ -1285,6 +1288,13 @@ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
1285 1288
             when 5..8; 'bigint'
1286 1289
             else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
1287 1290
           end
  1291
+        when 'datetime'
  1292
+          return super unless precision
  1293
+
  1294
+          case precision
  1295
+            when 0..6; "timestamp(#{precision})"
  1296
+            else raise(ActiveRecordError, "No timestamp type has precision of #{precision}. The allowed range of precision is from 0 to 6")
  1297
+          end
1288 1298
         else
1289 1299
           super
1290 1300
         end
65  activerecord/test/cases/adapters/postgresql/timestamp_test.rb
@@ -27,4 +27,69 @@ def test_save_infinity_and_beyond
27 27
     d = Developer.create!(:name => 'aaron', :updated_at => -1.0 / 0.0)
28 28
     assert_equal(-1.0 / 0.0, d.updated_at)
29 29
   end
  30
+
  31
+  def test_default_datetime_precision
  32
+    ActiveRecord::Base.connection.create_table(:foos)
  33
+    ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime
  34
+    ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime
  35
+    assert_nil activerecord_column_option('foos', 'created_at', 'precision')
  36
+  end
  37
+
  38
+  def test_timestamp_data_type_with_precision
  39
+    ActiveRecord::Base.connection.create_table(:foos)
  40
+    ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime, :precision => 0
  41
+    ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime, :precision => 5
  42
+    assert_equal 0, activerecord_column_option('foos', 'created_at', 'precision')
  43
+    assert_equal 5, activerecord_column_option('foos', 'updated_at', 'precision')
  44
+  end
  45
+
  46
+  def test_timestamps_helper_with_custom_precision
  47
+    ActiveRecord::Base.connection.create_table(:foos) do |t|
  48
+      t.timestamps :precision => 4
  49
+    end
  50
+    assert_equal 4, activerecord_column_option('foos', 'created_at', 'precision')
  51
+    assert_equal 4, activerecord_column_option('foos', 'updated_at', 'precision')
  52
+  end
  53
+
  54
+  def test_passing_precision_to_timestamp_does_not_set_limit
  55
+    ActiveRecord::Base.connection.create_table(:foos) do |t|
  56
+      t.timestamps :precision => 4
  57
+    end
  58
+    assert_nil activerecord_column_option("foos", "created_at", "limit")
  59
+    assert_nil activerecord_column_option("foos", "updated_at", "limit")
  60
+  end
  61
+
  62
+  def test_invalid_timestamp_precision_raises_error
  63
+    assert_raises ActiveRecord::ActiveRecordError do
  64
+      ActiveRecord::Base.connection.create_table(:foos) do |t|
  65
+        t.timestamps :precision => 7
  66
+      end
  67
+    end
  68
+  end
  69
+
  70
+  def test_postgres_agrees_with_activerecord_about_precision
  71
+    ActiveRecord::Base.connection.create_table(:foos) do |t|
  72
+      t.timestamps :precision => 4
  73
+    end
  74
+    assert_equal '4', pg_datetime_precision('foos', 'created_at')
  75
+    assert_equal '4', pg_datetime_precision('foos', 'updated_at')
  76
+  end
  77
+
  78
+  private
  79
+
  80
+    def pg_datetime_precision(table_name, column_name)
  81
+      results = ActiveRecord::Base.connection.execute("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name ='#{table_name}'")
  82
+      result = results.find do |result_hash|
  83
+        result_hash["column_name"] == column_name
  84
+      end
  85
+      result && result["datetime_precision"]
  86
+    end
  87
+
  88
+    def activerecord_column_option(tablename, column_name, option)
  89
+      result = ActiveRecord::Base.connection.columns(tablename).find do |column|
  90
+        column.name == column_name
  91
+      end
  92
+      result && result.send(option)
  93
+    end
  94
+
30 95
 end

0 notes on commit 6657ec9

Please sign in to comment.
Something went wrong with that request. Please try again.