Search code examples
ruby-on-railsrubytimezoneutc

ActiveRecord is not aware of timezones?


I have one question regarding to time zone

I have done the following yet, my activerecord is not still matching with my timezone when I do following query

def stock_by_date(date)
   UserStock.where("date(created_at) = ? and user_id = ? ", date , current_user.id)
end

I did the following in side my application.rb

config.active_record.default_timezone = :utc
config.active_record.time_zone_aware_attributes = true
config.beginning_of_week = :sunday
config.active_record.time_zone_aware_types = [:datetime, :time]

I added timezone field to my sign up method, and it shows the current day of timezone correctly, yet when I added a stock for the current day timezone, it shows up in either the previous day or next day of current daytime zone and when I added my stock, the timestamp is for a day before when I look at the rails console

My schema.rb

ActiveRecord::Schema.define(version: 2018_11_28_183416) do

  create_table "friendships", force: :cascade do |t|
    t.integer "user_id"
    t.integer "friend_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["friend_id"], name: "index_friendships_on_friend_id"
    t.index ["user_id"], name: "index_friendships_on_user_id"
  end

  create_table "notes", force: :cascade do |t|
    t.string "content"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.integer "user_id"
    t.index ["user_id"], name: "index_notes_on_user_id"
  end

  create_table "stocks", force: :cascade do |t|
    t.string "ticker"
    t.string "name"
    t.decimal "last_price"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "user_stocks", force: :cascade do |t|
    t.integer "user_id"
    t.integer "stock_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["stock_id"], name: "index_user_stocks_on_stock_id"
    t.index ["user_id"], name: "index_user_stocks_on_user_id"
  end

  create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "first_name"
    t.string "last_name"
    t.string "time_zone", default: "UTC"
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

end

for example, enter image description here enter image description here

How can I fix this issue?

Note: If more info is needed, please let me know

Note: for the matter of being concise this is my repo

Note: my configuration does not show a reliable behavior at all and I am very confused :/ and I have the same issue for note of a current day

----update-------------------

In my rails console

enter image description here


Solution

  • create a migration with this content to remove the default value to created_at, so it's not filled by default on database level.

    change_column_default(:user_stocks, :created_at, nil)
    

    after that, when you create a new UserStock, you need to specify the created_at value, and there you can specify the created_at with the date of the user, taking care of the timezone.

    UserStock.create(created_at: Time.now.in_time_zone(user.time_zone).to_time...)
    

    or you can just maybe add it to a callback on the model so it's automatic everytime you create a UserStock, it change the value

    class UserStock < ActiveRecord::Base
      before_create :set_created_at
    
      private
        def set_created_at
          self.created_at = time.now.in_time_zone(self.user.time_zone).to_time
        end
    end
    

    so everytime you create one, the value is correct. with that it may work as expected.

    Another option if you don't need it just for that specific model and use them for all the user interaction, you can create a before filter on the application controller to set the time zone dinamically, something like

    class ApplicationController < ActionController::Base
      before_filter :set_time_zone
    
      def set_time_zone(&block)
        time_zone = current_user.try(:time_zone) || 'UTC'
        Time.use_zone(time_zone, &block)
      end
    end