Commit 98b0ca62 authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch '46082-runner-contacted_at-is-not-always-a-time-type' into 'master'

Resolve "Runner#contacted_at is not always a Time type"

Closes #46082

See merge request gitlab-org/gitlab-ce!18810
parents de3f89a4 55e2ce76
...@@ -75,7 +75,7 @@ module Ci ...@@ -75,7 +75,7 @@ module Ci
project_type: 3 project_type: 3
} }
cached_attr_reader :version, :revision, :platform, :architecture, :contacted_at, :ip_address cached_attr_reader :version, :revision, :platform, :architecture, :ip_address, :contacted_at
chronic_duration_attr :maximum_timeout_human_readable, :maximum_timeout chronic_duration_attr :maximum_timeout_human_readable, :maximum_timeout
......
...@@ -7,7 +7,11 @@ module RedisCacheable ...@@ -7,7 +7,11 @@ module RedisCacheable
class_methods do class_methods do
def cached_attr_reader(*attributes) def cached_attr_reader(*attributes)
attributes.each do |attribute| attributes.each do |attribute|
define_method("#{attribute}") do define_method(attribute) do
unless self.has_attribute?(attribute)
raise ArgumentError, "`cached_attr_reader` requires the #{self.class.name}\##{attribute} attribute to have a database column"
end
cached_attribute(attribute) || read_attribute(attribute) cached_attribute(attribute) || read_attribute(attribute)
end end
end end
...@@ -15,13 +19,16 @@ module RedisCacheable ...@@ -15,13 +19,16 @@ module RedisCacheable
end end
def cached_attribute(attribute) def cached_attribute(attribute)
(cached_attributes || {})[attribute] cached_value = (cached_attributes || {})[attribute]
cast_value_from_cache(attribute, cached_value) if cached_value
end end
def cache_attributes(values) def cache_attributes(values)
Gitlab::Redis::SharedState.with do |redis| Gitlab::Redis::SharedState.with do |redis|
redis.set(cache_attribute_key, values.to_json, ex: CACHED_ATTRIBUTES_EXPIRY_TIME) redis.set(cache_attribute_key, values.to_json, ex: CACHED_ATTRIBUTES_EXPIRY_TIME)
end end
clear_memoization(:cached_attributes)
end end
private private
...@@ -38,4 +45,12 @@ module RedisCacheable ...@@ -38,4 +45,12 @@ module RedisCacheable
end end
end end
end end
def cast_value_from_cache(attribute, value)
if Gitlab.rails5?
self.class.type_for_attribute(attribute).cast(value)
else
self.class.column_for_attribute(attribute).type_cast_from_database(value)
end
end
end end
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
= tag = tag
%td %td
- if runner.contacted_at - if runner.contacted_at
#{time_ago_in_words(runner.contacted_at)} ago = time_ago_with_tooltip runner.contacted_at
- else - else
Never Never
%td.admin-runner-btn-group-cell %td.admin-runner-btn-group-cell
......
...@@ -66,6 +66,6 @@ ...@@ -66,6 +66,6 @@
%td Last contact %td Last contact
%td %td
- if @runner.contacted_at - if @runner.contacted_at
#{time_ago_in_words(@runner.contacted_at)} ago = time_ago_with_tooltip @runner.contacted_at
- else - else
Never Never
---
title: Fix Runner contacted at tooltip cache.
merge_request: 18810
author:
type: fixed
require 'spec_helper' require 'spec_helper'
describe RedisCacheable do describe RedisCacheable do
let(:model) { double } let(:model) do
Struct.new(:id, :attributes) do
def read_attribute(attribute)
attributes[attribute]
end
def cast_value_from_cache(attribute, cached_value)
cached_value
end
def has_attribute?(attribute)
attributes.has_key?(attribute)
end
end
end
let(:payload) { { name: 'value', time: Time.zone.now } }
let(:instance) { model.new(1, payload) }
let(:cache_key) { instance.__send__(:cache_attribute_key) }
before do before do
model.extend(described_class) model.include(described_class)
allow(model).to receive(:cache_attribute_key).and_return('key')
end end
describe '#cached_attribute' do describe '#cached_attribute' do
let(:payload) { { attribute: 'value' } } subject { instance.cached_attribute(payload.keys.first) }
subject { model.cached_attribute(payload.keys.first) }
it 'gets the cache attribute' do it 'gets the cache attribute' do
Gitlab::Redis::SharedState.with do |redis| Gitlab::Redis::SharedState.with do |redis|
expect(redis).to receive(:get).with('key') expect(redis).to receive(:get).with(cache_key)
.and_return(payload.to_json) .and_return(payload.to_json)
end end
...@@ -24,16 +39,62 @@ describe RedisCacheable do ...@@ -24,16 +39,62 @@ describe RedisCacheable do
end end
describe '#cache_attributes' do describe '#cache_attributes' do
let(:values) { { name: 'new_name' } } subject { instance.cache_attributes(payload) }
subject { model.cache_attributes(values) }
it 'sets the cache attributes' do it 'sets the cache attributes' do
Gitlab::Redis::SharedState.with do |redis| Gitlab::Redis::SharedState.with do |redis|
expect(redis).to receive(:set).with('key', values.to_json, anything) expect(redis).to receive(:set).with(cache_key, payload.to_json, anything)
end end
subject subject
end end
end end
describe '#cached_attr_reader', :clean_gitlab_redis_shared_state do
subject { instance.name }
before do
model.cached_attr_reader(:name)
end
context 'when there is no cached value' do
it 'reads the attribute' do
expect(instance).to receive(:read_attribute).and_call_original
expect(subject).to eq(payload[:name])
end
end
context 'when there is a cached value' do
it 'reads the cached value' do
expect(instance).not_to receive(:read_attribute)
instance.cache_attributes(payload)
expect(subject).to eq(payload[:name])
end
end
it 'always returns the latest values' do
expect(instance.name).to eq(payload[:name])
instance.cache_attributes(name: 'new_value')
expect(instance.name).to eq('new_value')
end
end
describe '#cast_value_from_cache' do
subject { instance.__send__(:cast_value_from_cache, attribute, value) }
context 'with runner contacted_at' do
let(:instance) { Ci::Runner.new }
let(:attribute) { :contacted_at }
let(:value) { '2018-05-07 13:53:08 UTC' }
it 'converts cache string to appropriate type' do
expect(subject).to be_an_instance_of(ActiveSupport::TimeWithZone)
end
end
end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment