second_level_cache 2.5.3 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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