second_level_cache 2.5.3 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 57109a059ebed4c9d448fa28e474f19eef4ebf7b67e4ebbd728f2be852b61480
4
- data.tar.gz: '049303d75a982adf35dfea344af63577dd9df1c09a28613bb4dec4332ef95de4'
3
+ metadata.gz: b6cfa929a72e1977223e5fc958b58ead3b3fa81daccbf38d4efe7e4ffc1d608d
4
+ data.tar.gz: 5709808afb6c5ee8d5937e1ce944005b528c4550e30d0db08b067864a5984d83
5
5
  SHA512:
6
- metadata.gz: 553a34ea48e7b3100ae819282c354ee0eb51e2d609b7c090bf477035b975a0e497158ca6f4b282add76c882fa7e94277fb39486aaf4e45c35c9e98088a2bbb34
7
- data.tar.gz: 28a7f7ea7f32467e92097824559fbaebaf3d9dd0fe49443d7779e666cce3270f3378c8e77dae91bffbc5248c8fadd10f00a08ebbbe390641fa9e42007b917ba5
6
+ metadata.gz: ee4e3935b1cf774fe00e1432878ffee6104ad5b15c5ad4d6994521b9d5bb7fa183fcbdd97ba3dfd509317f7cd30ebabc0b5f89c18c0dd7aaad028474ef3f7d74
7
+ data.tar.gz: 173d12fbd5b5b440e37f9d219f9229f0146751debd5c8fa18e54b684ea257ec81a707a46a5b1e39309f9b7bb07784c921ea6a2737d3a681bb7480c9bb8ca6ebf
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ 2.6.0
2
+ -------
3
+
4
+ - Add has_one through cache support. (#98)
5
+ - Fix string query, eager_load, includes/preload for fetch from db. ( #103, #102, #101)
6
+ - Fix preloader if exists default scope. (#104)
7
+ - Change cache hit log as `DEBUG` level. (#105)
8
+
1
9
  2.5.3
2
10
  -------
3
11
 
@@ -23,5 +23,5 @@ ActiveSupport.on_load(:active_record) do
23
23
  ActiveRecord::Relation.send(:prepend, SecondLevelCache::ActiveRecord::FinderMethods)
24
24
  # Rails 5.2 has removed ActiveRecord::Associations::Preloader::BelongsTo
25
25
  # https://github.com/rails/rails/pull/31079
26
- ActiveRecord::Associations::Preloader::Association.send(:prepend, SecondLevelCache::ActiveRecord::Associations::Preloader::BelongsTo)
26
+ ActiveRecord::Associations::Preloader::Association.send(:prepend, SecondLevelCache::ActiveRecord::Associations::Preloader)
27
27
  end
@@ -14,7 +14,7 @@ module SecondLevelCache
14
14
  nil
15
15
  end
16
16
  end
17
- return record if compare_record_attributes_with_where_values(record, where_values)
17
+ return record if record_attributes_equal_where_values?(record, where_values)
18
18
  record = where(where_values).first
19
19
  if record
20
20
  SecondLevelCache.cache_store.write(cache_key, record.id)
@@ -53,21 +53,9 @@ module SecondLevelCache
53
53
  "uniq_key_#{name}_#{ext_key}"
54
54
  end
55
55
 
56
- def compare_record_attributes_with_where_values(record, where_values)
57
- return false unless record
58
- where_values.all? do |k, v|
59
- attribute_value = record.read_attribute(k)
60
- attribute_value == case attribute_value
61
- when String
62
- v.to_s
63
- when Numeric
64
- v.to_f
65
- when Date
66
- v.to_date
67
- else # Maybe NilClass/?
68
- v
69
- end
70
- end
56
+ def record_attributes_equal_where_values?(record, where_values)
57
+ # https://api.rubyonrails.org/classes/ActiveRecord/ModelSchema/ClassMethods.html#method-i-type_for_attribute
58
+ where_values.all? { |k, v| record&.read_attribute(k) == type_for_attribute(k).cast(v) }
71
59
  end
72
60
  end
73
61
  end
@@ -17,20 +17,13 @@ module SecondLevelCache
17
17
  # Article.where("articles.user_id = 1").find(prams[:id])
18
18
  # Article.where("user_id = 1 AND ...").find(params[:id])
19
19
  def find_one(id)
20
- return super(id) unless second_level_cache_enabled?
21
- return super(id) unless select_all_column?
20
+ return super unless cachable?
22
21
 
23
22
  id = id.id if ActiveRecord::Base == id
24
- if cachable?
25
- record = @klass.read_second_level_cache(id)
26
- if record
27
- if where_values_hash.blank? || where_values_match_cache?(record)
28
- return record
29
- end
30
- end
31
- end
23
+ record = @klass.read_second_level_cache(id)
24
+ return record if record && where_values_match_cache?(record)
32
25
 
33
- record = super(id)
26
+ record = super
34
27
  record.write_second_level_cache
35
28
  record
36
29
  end
@@ -48,15 +41,16 @@ module SecondLevelCache
48
41
  # User.where(age: 18).first
49
42
  #
50
43
  def first(limit = nil)
51
- return super(limit) if limit.to_i > 1
44
+ return super if limit.to_i > 1
45
+ return super unless cachable?
52
46
  # only have primary_key condition in where
53
47
  if where_values_hash.length == 1 && where_values_hash.key?(primary_key)
54
48
  record = @klass.read_second_level_cache(where_values_hash[primary_key])
55
49
  return record if record
56
50
  end
57
51
 
58
- record = super(limit)
59
- record&.write_second_level_cache if select_all_column?
52
+ record = super
53
+ record&.write_second_level_cache
60
54
  record
61
55
  end
62
56
 
@@ -64,18 +58,31 @@ module SecondLevelCache
64
58
 
65
59
  # readonly_value - active_record/relation/query_methods.rb Rails 5.1 true/false
66
60
  def cachable?
67
- limit_one? &&
68
- order_values.blank? &&
61
+ second_level_cache_enabled? &&
62
+ limit_one? &&
63
+ # !eager_loading? &&
69
64
  includes_values.blank? &&
70
65
  preload_values.blank? &&
66
+ eager_load_values.blank? &&
67
+ select_values.blank? &&
68
+ order_values_can_cache? &&
71
69
  readonly_value.blank? &&
72
70
  joins_values.blank? &&
73
71
  !@klass.locking_enabled? &&
74
- where_clause_match_equality?
72
+ where_clause_predicates_all_equality?
73
+ end
74
+
75
+ def order_values_can_cache?
76
+ return true if order_values.empty?
77
+ return false unless order_values.one?
78
+ return true if order_values.first == klass.primary_key
79
+ return false unless order_values.first.is_a?(::Arel::Nodes::Ordering)
80
+ return true if order_values.first.expr == klass.primary_key
81
+ order_values.first.expr.try(:name) == klass.primary_key
75
82
  end
76
83
 
77
- def where_clause_match_equality?
78
- where_values_hash.all?
84
+ def where_clause_predicates_all_equality?
85
+ where_clause.send(:predicates).size == where_values_hash.size
79
86
  end
80
87
 
81
88
  def where_values_match_cache?(record)
@@ -91,10 +98,6 @@ module SecondLevelCache
91
98
  def limit_one?
92
99
  limit_value.blank? || limit_value == 1
93
100
  end
94
-
95
- def select_all_column?
96
- select_values.blank?
97
- end
98
101
  end
99
102
  end
100
103
  end
@@ -6,31 +6,25 @@ module SecondLevelCache
6
6
  module HasOneAssociation
7
7
  def find_target
8
8
  return super unless klass.second_level_cache_enabled?
9
- return super if reflection.options[:through] || reflection.scope
10
- # TODO: implement cache with has_one through, scope
9
+ return super if reflection.scope
10
+ # TODO: implement cache with has_one scope
11
11
 
12
- owner_primary_key = owner[reflection.active_record_primary_key]
13
- if reflection.options[:as]
14
- keys = {
15
- reflection.foreign_key => owner_primary_key,
16
- reflection.type => owner.class.base_class.name
17
- }
18
- cache_record = klass.fetch_by_uniq_keys(keys)
19
- else
20
- cache_record = klass.fetch_by_uniq_key(owner_primary_key, reflection.foreign_key)
21
- end
12
+ through = reflection.options[:through]
13
+ record = if through
14
+ return super unless klass.reflections[through.to_s].klass.second_level_cache_enabled?
15
+ begin
16
+ reflection.klass.find(owner.send(through).read_attribute(reflection.foreign_key))
17
+ rescue StandardError
18
+ nil
19
+ end
20
+ else
21
+ uniq_keys = { reflection.foreign_key => owner[reflection.active_record_primary_key] }
22
+ uniq_keys[reflection.type] = owner.class.base_class.name if reflection.options[:as]
23
+ klass.fetch_by_uniq_keys(uniq_keys)
24
+ end
22
25
 
23
- if cache_record
24
- return cache_record.tap { |record| set_inverse_instance(record) }
25
- end
26
-
27
- record = super
28
26
  return nil unless record
29
-
30
- record.tap do |r|
31
- set_inverse_instance(r)
32
- r.write_second_level_cache
33
- end
27
+ record.tap { |r| set_inverse_instance(r) }
34
28
  end
35
29
  end
36
30
  end
@@ -3,41 +3,47 @@
3
3
  module SecondLevelCache
4
4
  module ActiveRecord
5
5
  module Associations
6
- class Preloader
7
- module BelongsTo
8
- def records_for(ids, &block)
9
- return super(ids, &block) unless reflection.is_a?(::ActiveRecord::Reflection::BelongsToReflection)
10
- return super(ids, &block) unless klass.second_level_cache_enabled?
11
-
12
- map_cache_keys = ids.map { |id| klass.second_level_cache_key(id) }
13
- records_from_cache = ::SecondLevelCache.cache_store.read_multi(*map_cache_keys)
14
- # NOTICE
15
- # Rails.cache.read_multi return hash that has keys only hitted.
16
- # eg. Rails.cache.read_multi(1,2,3) => {2 => hit_value, 3 => hit_value}
17
- hitted_ids = records_from_cache.map { |key, _| key.split("/")[2] }
18
- missed_ids = ids.map(&:to_s) - hitted_ids
19
-
20
- ::SecondLevelCache.logger.info "missed ids -> #{missed_ids.join(',')} | hitted ids -> #{hitted_ids.join(',')}"
21
-
22
- record_marshals = RecordMarshal.load_multi(records_from_cache.values)
23
-
24
- if missed_ids.empty?
25
- return SecondLevelCache::RecordRelation.new(record_marshals)
26
- end
27
-
28
- records_from_db = super(missed_ids, &block)
29
- records_from_db.map do |r|
30
- write_cache(r)
31
- end
32
-
33
- SecondLevelCache::RecordRelation.new(records_from_db + record_marshals)
34
- end
6
+ module Preloader
7
+ RAILS6 = ::ActiveRecord.version >= ::Gem::Version.new("6")
8
+
9
+ def records_for(ids, &block)
10
+ return super unless klass.second_level_cache_enabled?
11
+ return super unless reflection.is_a?(::ActiveRecord::Reflection::BelongsToReflection)
12
+ return super if klass.default_scopes.present?
13
+
14
+ map_cache_keys = ids.map { |id| klass.second_level_cache_key(id) }
15
+ records_from_cache = ::SecondLevelCache.cache_store.read_multi(*map_cache_keys)
16
+
17
+ record_marshals = if RAILS6
18
+ RecordMarshal.load_multi(records_from_cache.values) do |record|
19
+ # This block is copy from:
20
+ # https://github.com/rails/rails/blob/6-0-stable/activerecord/lib/active_record/associations/preloader/association.rb#L101
21
+ owner = owners_by_key[convert_key(record[association_key_name])].first
22
+ association = owner.association(reflection.name)
23
+ association.set_inverse_instance(record)
24
+ end
25
+ else
26
+ RecordMarshal.load_multi(records_from_cache.values, &block)
27
+ end
28
+
29
+ # NOTICE
30
+ # Rails.cache.read_multi return hash that has keys only hitted.
31
+ # eg. Rails.cache.read_multi(1,2,3) => {2 => hit_value, 3 => hit_value}
32
+ hitted_ids = record_marshals.map { |record| record.read_attribute(association_key_name).to_s }
33
+ missed_ids = ids.map(&:to_s) - hitted_ids
34
+ ::SecondLevelCache.logger.debug("missed #{association_key_name} -> #{missed_ids.join(',')} | hitted #{association_key_name} -> #{hitted_ids.join(',')}")
35
+ return SecondLevelCache::RecordRelation.new(record_marshals) if missed_ids.empty?
36
+
37
+ records_from_db = super(missed_ids, &block)
38
+ records_from_db.map { |r| write_cache(r) }
39
+
40
+ SecondLevelCache::RecordRelation.new(records_from_db + record_marshals)
41
+ end
35
42
 
36
- private
43
+ private
37
44
 
38
- def write_cache(record)
39
- record.write_second_level_cache
40
- end
45
+ def write_cache(record)
46
+ record.write_second_level_cache
41
47
  end
42
48
  end
43
49
  end
@@ -76,18 +76,8 @@ module SecondLevelCache
76
76
 
77
77
  marshal = RecordMarshal.dump(self)
78
78
  expires_in = klass.second_level_cache_options[:expires_in]
79
- expire_changed_association_uniq_keys
80
79
  SecondLevelCache.cache_store.write(second_level_cache_key, marshal, expires_in: expires_in)
81
80
  end
82
-
83
81
  alias update_second_level_cache write_second_level_cache
84
-
85
- def expire_changed_association_uniq_keys
86
- reflections = klass.reflections.select { |_, reflection| reflection.belongs_to? }
87
- changed_keys = reflections.map { |_, reflection| reflection.foreign_key } & previous_changes.keys
88
- changed_keys.each do |key|
89
- SecondLevelCache.cache_store.delete(klass.send(:cache_uniq_key, key => previous_changes[key][0]))
90
- end
91
- end
92
82
  end
93
83
  end
@@ -2,54 +2,21 @@
2
2
 
3
3
  module RecordMarshal
4
4
  class << self
5
- # dump ActiveRecord instace with only attributes.
6
- # ["User",
7
- # {"id"=>30,
8
- # "email"=>"dddssddd@gmail.com",
9
- # "created_at"=>2012-07-25 18:25:57 UTC
10
- # }
11
- # ]
12
-
5
+ # dump ActiveRecord instance with only attributes before type cast.
13
6
  def dump(record)
14
- [record.class.name, record.attributes]
7
+ [record.class.name, record.attributes_before_type_cast]
15
8
  end
16
9
 
17
10
  # load a cached record
18
- def load(serialized)
11
+ def load(serialized, &block)
19
12
  return unless serialized
20
- # fix issues 19
21
- # fix 2.1.2 object.changed? ActiveRecord::SerializationTypeMismatch: Attribute was supposed to be a Hash, but was a String. -- "{:a=>\"t\", :b=>\"x\"}"
22
- # fix 2.1.4 object.changed? is true
23
- # fix Rails 4.2 is deprecating `serialized_attributes` without replacement to Rails 5 is deprecating `serialized_attributes` without replacement
24
- klass = serialized[0].constantize
25
- attributes = serialized[1]
26
-
27
- # for ActiveRecord 5.0.0
28
- klass.columns.each do |c|
29
- name = c.name
30
- cast_type = klass.attribute_types[name]
31
- next unless cast_type.is_a?(::ActiveRecord::Type::Serialized)
32
- coder = cast_type.coder
33
- next if attributes[name].nil? || attributes[name].is_a?(String)
34
- if coder.is_a?(::ActiveRecord::Coders::YAMLColumn)
35
- attributes[name] = coder.dump(attributes[name]) if attributes[name].is_a?(coder.object_class)
36
- elsif coder.is_a?(::ActiveRecord::Store::IndifferentCoder)
37
- # https://github.com/rails/rails/blob/5b14129/activerecord/lib/active_record/store.rb#L179
38
- attributes[name] = coder.dump(attributes[name])
39
- elsif coder == ::ActiveRecord::Coders::JSON
40
- attributes[name] = attributes[name].to_json
41
- end
42
- end
43
-
44
- klass.defined_enums.each do |key, value|
45
- attributes[key] = value[attributes[key]] if attributes[key].is_a?(String)
46
- end
47
13
 
48
- klass.instantiate(attributes)
14
+ serialized[0].constantize.instantiate(serialized[1], &block)
49
15
  end
50
16
 
51
- def load_multi(serializeds)
52
- serializeds.map { |serialized| load(serialized) }
17
+ # load multi cached records
18
+ def load_multi(serializeds, &block)
19
+ serializeds.map { |serialized| load(serialized, &block) }
53
20
  end
54
21
  end
55
22
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SecondLevelCache
4
- VERSION = "2.5.3"
4
+ VERSION = "2.6.0"
5
5
  end
@@ -17,15 +17,18 @@ class FetchByUinqKeyTest < ActiveSupport::TestCase
17
17
  assert Contribution.send(:cache_uniq_key, user_id: 1, date: Time.current.to_date), "uniq_key_Contribution_user_id_1,date_#{Time.current.to_date}"
18
18
  end
19
19
 
20
- def test_compare_record_attributes_with_where_values
21
- book = Book.new(title: "foobar")
22
- assert Book.send(:compare_record_attributes_with_where_values, book, title: :foobar)
23
- book.discount_percentage = 60.00
24
- assert Book.send(:compare_record_attributes_with_where_values, book, discount_percentage: "60")
25
- book.publish_date = Time.current.to_date
26
- assert Book.send(:compare_record_attributes_with_where_values, book, publish_date: Time.current.to_date.to_s)
27
- book.title = nil
28
- assert Book.send(:compare_record_attributes_with_where_values, book, title: nil)
20
+ def test_record_attributes_equal_where_values
21
+ book = Book.new
22
+ assert_no_queries do
23
+ book.title = "foobar"
24
+ assert Book.send(:record_attributes_equal_where_values?, book, title: :foobar)
25
+ book.discount_percentage = 60.00
26
+ assert Book.send(:record_attributes_equal_where_values?, book, discount_percentage: "60")
27
+ book.publish_date = Time.current.to_date
28
+ assert Book.send(:record_attributes_equal_where_values?, book, publish_date: Time.current.to_date.to_s)
29
+ book.title = nil
30
+ assert Book.send(:record_attributes_equal_where_values?, book, title: nil)
31
+ end
29
32
  end
30
33
 
31
34
  def test_should_query_from_db_using_primary_key
@@ -59,6 +59,45 @@ class FinderMethodsTest < ActiveSupport::TestCase
59
59
  refute_equal @user.name, @from_db.name
60
60
  end
61
61
 
62
+ def test_should_fetch_from_db_if_where_use_string
63
+ @user.write_second_level_cache
64
+ assert_queries(:any) do
65
+ assert_nil User.unscoped.where(id: @user.id).where("name = 'nonexistent'").first
66
+ end
67
+ assert_queries(:any) do
68
+ assert_raises ActiveRecord::RecordNotFound do
69
+ User.where("name = 'nonexistent'").find(@user.id)
70
+ end
71
+ end
72
+ end
73
+
74
+ def test_should_fetch_from_db_when_use_eager_load
75
+ @user.write_second_level_cache
76
+ assert_queries(:any) do
77
+ assert_sql(/LEFT\sOUTER\sJOIN\s\"books\"/m) do
78
+ User.eager_load(:books).find(@user.id)
79
+ end
80
+ end
81
+ end
82
+
83
+ def test_should_fetch_from_db_when_use_includes
84
+ @user.write_second_level_cache
85
+ assert_queries(:any) do
86
+ assert_sql(/SELECT\s\"books\"\.\*\sFROM\s\"books\"/m) do
87
+ User.includes(:books).find(@user.id)
88
+ end
89
+ end
90
+ end
91
+
92
+ def test_should_fetch_from_db_when_use_preload
93
+ @user.write_second_level_cache
94
+ assert_queries(:any) do
95
+ assert_sql(/SELECT\s\"books\"\.\*\sFROM\s\"books\"/m) do
96
+ User.preload(:books).find(@user.id)
97
+ end
98
+ end
99
+ end
100
+
62
101
  def test_where_and_first_should_with_cache
63
102
  @user.write_second_level_cache
64
103
  assert_no_queries do
@@ -20,10 +20,16 @@ class HasOneAssociationTest < ActiveSupport::TestCase
20
20
  clean_user = user.reload
21
21
  assert_equal User, clean_user.forked_from_user.class
22
22
  assert_equal @user.id, user.forked_from_user.id
23
- # clean_user = user.reload
24
- # assert_no_queries do
25
- # clean_user.forked_from_user
26
- # end
23
+ # If ForkedUserLink second_level_cache_enabled is true
24
+ user.reload
25
+ assert_no_queries do
26
+ user.forked_from_user
27
+ end
28
+ # IF ForkedUserLink second_level_cache_enabled is false
29
+ user.reload
30
+ ForkedUserLink.without_second_level_cache do
31
+ assert_queries(1) { user.forked_from_user }
32
+ end
27
33
  end
28
34
 
29
35
  def test_has_one_with_conditions
@@ -49,4 +55,10 @@ class HasOneAssociationTest < ActiveSupport::TestCase
49
55
  @account.update(user_id: @user.id + 1)
50
56
  assert_nil @user.reload.account
51
57
  end
58
+
59
+ def test_should_one_query_when_has_one_target_is_null
60
+ Namespace.destroy_all
61
+ @user.reload
62
+ assert_queries(1) { @user.namespace }
63
+ end
52
64
  end
@@ -9,5 +9,5 @@ end
9
9
 
10
10
  class Account < ActiveRecord::Base
11
11
  second_level_cache expires_in: 3.days
12
- belongs_to :user
12
+ belongs_to :user, foreign_key: :user_id, inverse_of: :account
13
13
  end
data/test/model/book.rb CHANGED
@@ -7,11 +7,14 @@ ActiveRecord::Base.connection.create_table(:books, force: true) do |t|
7
7
  t.decimal :discount_percentage, precision: 5, scale: 2
8
8
  t.integer :images_count, default: 0
9
9
  t.date :publish_date
10
+ t.boolean :normal, default: true, nil: false
10
11
  end
11
12
 
12
13
  class Book < ActiveRecord::Base
13
14
  second_level_cache
14
15
 
16
+ default_scope -> { where(normal: true) }
17
+
15
18
  belongs_to :user, counter_cache: true
16
19
  has_many :images, as: :imagable
17
20
  end
data/test/model/user.rb CHANGED
@@ -10,7 +10,7 @@ ActiveRecord::Base.connection.create_table(:users, force: true) do |t|
10
10
  t.integer :books_count, default: 0
11
11
  t.integer :images_count, default: 0
12
12
  t.datetime :deleted_at
13
- t.timestamps null: false
13
+ t.timestamps null: false, precision: 6
14
14
  end
15
15
 
16
16
  ActiveRecord::Base.connection.create_table(:forked_user_links, force: true) do |t|
@@ -35,7 +35,7 @@ class User < ActiveRecord::Base
35
35
  serialize :json_options, JSON if ::ActiveRecord::VERSION::STRING >= "4.1.0"
36
36
  store :extras, accessors: %i[tagline gender]
37
37
 
38
- has_one :account
38
+ has_one :account, inverse_of: :user
39
39
  has_one :forked_user_link, foreign_key: "forked_to_user_id"
40
40
  has_one :forked_from_user, through: :forked_user_link
41
41
  has_many :namespaces
@@ -53,6 +53,8 @@ class Namespace < ActiveRecord::Base
53
53
  end
54
54
 
55
55
  class ForkedUserLink < ActiveRecord::Base
56
+ second_level_cache version: 1, expires_in: 1.day
57
+
56
58
  belongs_to :forked_from_user, class_name: "User"
57
59
  belongs_to :forked_to_user, class_name: "User"
58
60
  end
@@ -2,8 +2,8 @@
2
2
 
3
3
  require "test_helper"
4
4
 
5
- class PreloaderTest < ActiveSupport::TestCase
6
- def test_belongs_to_preload_caches_includes
5
+ class PreloaderBelongsToTest < ActiveSupport::TestCase
6
+ def test_preload_caches_includes
7
7
  topics = [
8
8
  Topic.create(title: "title1", body: "body1"),
9
9
  Topic.create(title: "title2", body: "body2"),
@@ -18,7 +18,7 @@ class PreloaderTest < ActiveSupport::TestCase
18
18
  assert_equal topics, results.map(&:topic)
19
19
  end
20
20
 
21
- def test_belongs_to_when_read_multi_missed_from_cache_ar_will_fetch_missed_records_from_db
21
+ def test_when_read_multi_missed_from_cache_ar_will_fetch_missed_records_from_db
22
22
  topics = [
23
23
  Topic.create(title: "title1", body: "body1"),
24
24
  Topic.create(title: "title2", body: "body2"),
@@ -39,19 +39,21 @@ class PreloaderTest < ActiveSupport::TestCase
39
39
  assert_equal topics, results.map(&:topic)
40
40
  end
41
41
 
42
- def test_has_many_preloader_returns_correct_results
43
- topic = Topic.create(id: 1)
44
- Post.create(id: 1)
45
- post = topic.posts.create
46
-
47
- assert_equal [post], Topic.includes(:posts).find(1).posts
42
+ def test_preloader_caches_includes_tried_set_inverse_instance
43
+ user_id = Time.current.to_i
44
+ Account.create(site: "foobar", user_id: user_id)
45
+ User.create(id: user_id, name: "foobar", email: "foobar@test.com")
46
+ accounts = Account.includes(:user)
47
+ assert_equal accounts.first.object_id, accounts.first.user.account.object_id
48
48
  end
49
49
 
50
- def test_has_one_preloader_returns_correct_results
51
- user = User.create(id: 1)
52
- Account.create(id: 1)
53
- account = user.create_account
54
-
55
- assert_equal account, User.includes(:account).find(1).account
50
+ def test_preloader_from_db_when_exists_scope
51
+ user = User.create
52
+ book = user.books.create
53
+ image = book.images.create
54
+ book.toggle!(:normal)
55
+ assert_queries(:any) do
56
+ assert_nil Image.includes(:imagable).where(id: image.id).first.imagable
57
+ end
56
58
  end
57
59
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class PreloaderHasManyTest < ActiveSupport::TestCase
6
+ def test_preloader_returns_correct_records
7
+ topic = Topic.create(id: 1)
8
+ Post.create(id: 1)
9
+ post = topic.posts.create
10
+
11
+ assert_equal [post], Topic.includes(:posts).find(1).posts
12
+ end
13
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class PreloaderHasOneTest < ActiveSupport::TestCase
6
+ # def test_preload_caches_includes
7
+ # users = User.create([
8
+ # { name: "foobar1", email: "foobar1@test.com" },
9
+ # { name: "foobar2", email: "foobar2@test.com" },
10
+ # { name: "foobar3", email: "foobar3@test.com" }
11
+ # ])
12
+ # namespaces = users.map { |user| user.create_namespace(name: user.name) }
13
+
14
+ # assert_queries(2) { User.includes(:namespace).order(id: :asc).to_a } # Write cache
15
+ # assert_queries(1) do
16
+ # assert_equal namespaces, User.includes(:namespace).order(id: :asc).map(&:namespace)
17
+ # end
18
+ # end
19
+
20
+ # def test_when_read_multi_missed_from_cache_should_will_fetch_missed_records_from_db
21
+ # users = User.create([
22
+ # { name: "foobar1", email: "foobar1@test.com" },
23
+ # { name: "foobar2", email: "foobar2@test.com" },
24
+ # { name: "foobar3", email: "foobar3@test.com" }
25
+ # ])
26
+ # namespaces = users.map { |user| user.create_namespace(name: user.name) }
27
+ # assert_queries(2) { User.includes(:namespace).order(id: :asc).to_a } # Write cache
28
+ # expired_namespace = namespaces.first
29
+ # expired_namespace.expire_second_level_cache
30
+
31
+ # assert_queries(2) do
32
+ # assert_sql(/WHERE\s\"namespaces\".\"kind\"\sIS\sNULL\sAND\s\"namespaces\"\.\"user_id\"\s=\s?/m) do
33
+ # results = User.includes(:namespace).order(id: :asc).to_a
34
+ # assert_equal namespaces, results.map(&:namespace)
35
+ # assert_equal expired_namespace, results.first.namespace
36
+ # end
37
+ # end
38
+ # end
39
+
40
+ # def test_preloader_returns_correct_records_after_modify
41
+ # user = User.create(name: "foobar", email: "foobar@test.com")
42
+
43
+ # old_namespace = user.create_namespace(name: "old")
44
+ # assert_queries(2) { User.includes(:namespace).order(id: :asc).to_a } # Write cache
45
+ # assert_queries(1) do
46
+ # assert_equal old_namespace, User.includes(:namespace).first.namespace
47
+ # end
48
+
49
+ # new_namespace = user.create_namespace(name: "new")
50
+ # assert_queries(2) { User.includes(:namespace).order(id: :asc).to_a } # Write cache
51
+ # assert_queries(1) do
52
+ # assert_equal new_namespace, User.includes(:namespace).first.namespace
53
+ # end
54
+ # end
55
+
56
+ # def test_preloader_caches_includes_tried_set_inverse_instance
57
+ # User.create(name: "foobar", email: "foobar@test.com").create_account(site: "foobar")
58
+ # users = User.includes(:account)
59
+ # assert_equal users.first.object_id, users.first.account.user.object_id
60
+ # end
61
+
62
+ def test_has_one_preloader_returns_correct_results
63
+ user = User.create(id: 1)
64
+ Account.create(id: 1)
65
+ account = user.create_account
66
+
67
+ assert_equal account, User.includes(:account).find(1).account
68
+ end
69
+ end
@@ -25,7 +25,7 @@ class RecordMarshalTest < ActiveSupport::TestCase
25
25
  dumped = RecordMarshal.dump(@user)
26
26
  assert dumped.is_a?(Array)
27
27
  assert_equal "User", dumped[0]
28
- assert_equal @user.attributes, dumped[1]
28
+ assert_equal dumped[1], ActiveRecord::Base.connection.select_all(User.where(id: @user.id)).first
29
29
  end
30
30
 
31
31
  def test_should_load_active_record_object
data/test/test_helper.rb CHANGED
@@ -33,7 +33,7 @@ SecondLevelCache.configure do |config|
33
33
  config.cache_store = ActiveSupport::Cache::MemoryStore.new
34
34
  end
35
35
 
36
- SecondLevelCache.logger.level = Logger::ERROR
36
+ SecondLevelCache.logger.level = Logger::INFO
37
37
  ActiveSupport::Cache::MemoryStore.logger = SecondLevelCache.logger
38
38
  ActiveRecord::Base.logger = SecondLevelCache.logger
39
39
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: second_level_cache
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.3
4
+ version: 2.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hooopo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-26 00:00:00.000000000 Z
11
+ date: 2020-05-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -153,8 +153,10 @@ files:
153
153
  - test/model/user.rb
154
154
  - test/persistence_test.rb
155
155
  - test/polymorphic_association_test.rb
156
+ - test/preloader_belongs_to_test.rb
157
+ - test/preloader_has_many_test.rb
158
+ - test/preloader_has_one_test.rb
156
159
  - test/preloader_non_integer_test.rb
157
- - test/preloader_test.rb
158
160
  - test/record_marshal_test.rb
159
161
  - test/require_test.rb
160
162
  - test/second_level_cache_test.rb
@@ -188,15 +190,17 @@ summary: 'SecondLevelCache is a write-through and read-through caching library i
188
190
  is a cache miss, it will populate the cache. Write-Through: As objects are created,
189
191
  updated, and deleted, all of the caches are automatically kept up-to-date and coherent.'
190
192
  test_files:
193
+ - test/preloader_belongs_to_test.rb
191
194
  - test/polymorphic_association_test.rb
192
195
  - test/require_test.rb
193
- - test/preloader_test.rb
194
196
  - test/finder_methods_test.rb
197
+ - test/preloader_has_one_test.rb
195
198
  - test/preloader_non_integer_test.rb
196
199
  - test/belongs_to_association_test.rb
197
200
  - test/second_level_cache_test.rb
198
201
  - test/enum_attr_test.rb
199
202
  - test/record_marshal_test.rb
203
+ - test/preloader_has_many_test.rb
200
204
  - test/persistence_test.rb
201
205
  - test/single_table_inheritance_test.rb
202
206
  - test/model/image.rb