much-rails-redis-record 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 50c638b21e0db5c3a18f560c9f1bc4f26dc7e16ad6a29131b34f3cc4bb98b9ed
4
+ data.tar.gz: 157631dbd216beca52ca5181947e26cc45250d44450c2707e59f6942158101cd
5
+ SHA512:
6
+ metadata.gz: 6dbdca11c92091aab89d74e4e5d1c251b8ebe926ca827de6208df201c029cfad6ab3cff27db0905d61c4a5faa19c8fec139f42de259aa6abbc37146e342bfdd3
7
+ data.tar.gz: c6662b24f14d00893b0f706ed755d8fa9c3726a7217ee544ce85682bb9204ddcd6037535fe495f273329348c52bc4830f35092dc3efa6e8f7427cf95fd017b0e
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ ruby "~> 2.5"
6
+
7
+ gemspec
8
+
9
+ gem "pry"
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2021-Present Kelly Redding and Collin Redding
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,165 @@
1
+ # MuchRailsRedisRecord
2
+
3
+ Store records in Redis with MuchRails.
4
+
5
+ Note: I find redis to be a good choice for one-off "token" type records or any temporary record you want removed after a certain amount of time. MuchRailsRedisRecord is ideal for these types of records.
6
+
7
+ For typical records in Rails, ActiveRecord should be preferred.
8
+
9
+ ## Setup
10
+
11
+ ### Mixin on your record object you want persisted in Redis, e.g.:
12
+
13
+ ```ruby
14
+ class Thing
15
+ include MuchRailsRedisRecord
16
+
17
+ def self.TTL_SECS
18
+ @ttl_secs ||= 5 * 60 # 5 minutes
19
+ end
20
+
21
+ def self.REDIS_KEY_NAMESPACE
22
+ "things"
23
+ end
24
+
25
+ attr_accessor :name
26
+
27
+ def initialize(name:, **kargs)
28
+ super(**kargs)
29
+
30
+ @name = name
31
+ end
32
+
33
+ def to_h
34
+ {
35
+ "name" => name,
36
+ }
37
+ end
38
+
39
+ private
40
+
41
+ def on_validate
42
+ validate_presence
43
+ end
44
+
45
+ def validate_presence
46
+ errors[:name] << "can't be blank" if name.blank?
47
+ end
48
+ end
49
+ ```
50
+
51
+ ### Configure the redis connection
52
+
53
+ In e.g. `config/initializers/much_rails.rb`:
54
+
55
+ ```ruby
56
+ MuchRailsRedisRecord.config.redis =
57
+ HellaRedis.new({
58
+ url: ENV.fetch("REDIS_URL"){ "redis://localhost:6379/0" },
59
+ driver: "ruby",
60
+ redis_ns: "my-app",
61
+ size: ENV.fetch("APP_MAX_THREADS"){ 5 },
62
+ timeout: 1,
63
+ })
64
+ ```
65
+
66
+ ## Usage
67
+
68
+ ```
69
+ $ rails c
70
+ Loading development environment (Rails 6.1.3.1)
71
+ [1] pry(main)> chair = Thing.new(name: "Chair")
72
+ => #<Thing:0x00007fd404182358 @created_at=nil, @identifier=nil, @name="Chair", @updated_at=nil, @valid=nil>
73
+ [2] pry(main)> chair.save!
74
+ => true
75
+ [3] pry(main)> chair
76
+ => #<Thing:0x00007fd404182358 @created_at=2021-06-17 13:16:52.059503 UTC, @errors={}, @identifier="6600bdd3-4dc8-447b-910a-3cf079eaae98", @name="Chair", @updated_at=2021-06-17 13:16:52.059506 UTC, @valid=nil>
77
+ [4] pry(main)> chair.name = "Comfy chair"
78
+ => "Comfy chair"
79
+ [5] pry(main)> chair.save!
80
+ => true
81
+ [6] pry(main)> chair
82
+ => #<Thing:0x00007fd404182358 @created_at=2021-06-17 13:16:52.059503 UTC, @errors={}, @identifier="6600bdd3-4dc8-447b-910a-3cf079eaae98", @name="Comfy chair", @updated_at=2021-06-17 13:17:12.409879 UTC, @valid=nil>
83
+ [7] pry(main)> chair.valid?
84
+ => true
85
+ [8] pry(main)> chair.name = nil
86
+ => nil
87
+ [9] pry(main)> chair.valid?
88
+ => false
89
+ [10] pry(main)> chair.save!
90
+ MuchRails::InvalidError: {"name"=>["can't be blank"]}
91
+ from /Users/kelly/projects/redding/gems/much-rails-redis-record/lib/much-rails-redis-record.rb:156:in `validate!'
92
+ [11] pry(main)> chair.name = "Comfy chair"
93
+ => "Comfy chair"
94
+ [12] pry(main)> chair.valid?
95
+ => true
96
+ [13] pry(main)> chair.ttl
97
+ => 242
98
+ [14] pry(main)> chair.ttl
99
+ => 239
100
+ [15] pry(main)> chair.ttl
101
+ => 235
102
+ [16] pry(main)> chair.redis_key
103
+ => "things:6600bdd3-4dc8-447b-910a-3cf079eaae98"
104
+ [17] pry(main)> Thing.find_by_identifier("6600bdd3-4dc8-447b-910a-3cf079eaae98")
105
+ => #<Thing:0x00007fd4043d97c8 @created_at=2021-06-17 13:16:52 UTC, @identifier="6600bdd3-4dc8-447b-910a-3cf079eaae98", @name="Comfy chair", @updated_at=2021-06-17 13:17:12 UTC, @valid=nil>
106
+ [18] pry(main)> chair == Thing.find_by_identifier("6600bdd3-4dc8-447b-910a-3cf079eaae98")
107
+ => true
108
+ [19] pry(main)> Thing.find_all
109
+ => [#<Thing:0x00007fd40440af30 @created_at=2021-06-17 13:16:52 UTC, @identifier="6600bdd3-4dc8-447b-910a-3cf079eaae98", @name="Comfy chair", @updated_at=2021-06-17 13:17:12 UTC, @valid=nil>]
110
+ [20] pry(main)> chair.destroy!
111
+ => true
112
+ [21] pry(main)> Thing.find_all
113
+ => []
114
+ [22] pry(main)>
115
+ ```
116
+
117
+ ## Testing
118
+
119
+ ### Fake redis records
120
+
121
+ Use `MuchRailsRedisRecord::FakeBehaviors` to create test doubles for your redis records. The test doubles have the same API but won't call out to redis to persist.
122
+
123
+ E.g.
124
+
125
+ ```ruby
126
+ require "much-rails-redis-record/fake_behaviors"
127
+
128
+ class Factory::FakeThing < Thing
129
+ include MuchRailsRedisRecord::FakeBehaviors
130
+
131
+ def initialize(name: nil, **kargs)
132
+ super(name || Factory.string, **kargs)
133
+ end
134
+ end
135
+ ```
136
+
137
+ ```
138
+ [3] pry(main)> fake_thing = Factory::FakeThing.new
139
+ => #<Factory::FakeThing:0x00007f98b0865740 @created_at=2000-12-21 05:11:53 UTC, @identifier="6c8f6609-b459-4a8f-b5e2-df1bb02efbaa", @name="ygzzysbhip", @updated_at=2000-04-01 02:17:30 UTC, @valid=nil>
140
+ [4] pry(main)> Thing.find_all
141
+ => []
142
+ [5] pry(main)>
143
+ ```
144
+
145
+ ## Installation
146
+
147
+ Add this line to your application's Gemfile:
148
+
149
+ gem "much-rails-redis-record"
150
+
151
+ And then execute:
152
+
153
+ $ bundle
154
+
155
+ Or install it yourself as:
156
+
157
+ $ gem install much-rails-redis-record
158
+
159
+ ## Contributing
160
+
161
+ 1. Fork it
162
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
163
+ 3. Commit your changes (`git commit -am "Added some feature"`)
164
+ 4. Push to the branch (`git push origin my-new-feature`)
165
+ 5. Create new Pull Request
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "much-rails"
4
+ require "hella-redis"
5
+ require "much-rails-redis-record/version"
6
+
7
+ module MuchRailsRedisRecord
8
+ include MuchRails::Config
9
+ include MuchRails::Mixin
10
+
11
+ add_config
12
+
13
+ mixin_included do
14
+ attr_accessor :identifier, :created_at, :updated_at
15
+ end
16
+
17
+ mixin_class_methods do
18
+ def TTL_SECS
19
+ raise NotImplementedError
20
+ end
21
+
22
+ def REDIS_KEY_NAMESPACE
23
+ raise NotImplementedError
24
+ end
25
+
26
+ def redis
27
+ MuchRailsRedisRecord.config.redis
28
+ end
29
+
30
+ def redis_key(identifier)
31
+ "#{self.REDIS_KEY_NAMESPACE}:#{identifier}"
32
+ end
33
+
34
+ def find_by_identifier(identifier)
35
+ find_by_redis_key(redis_key(identifier))
36
+ end
37
+
38
+ def find_by_redis_key(redis_key)
39
+ args =
40
+ MuchRails::JSON.decode(
41
+ redis.connection do |c|
42
+ unless c.exists?(redis_key)
43
+ raise MuchRailsRedisRecord::NotFoundError
44
+ end
45
+
46
+ c.get(redis_key)
47
+ end,
48
+ )
49
+ new(**args.symbolize_keys)
50
+ end
51
+
52
+ # Note: this should not be used in production code for performance / memory
53
+ # consumption reasons. This should only be used for debugging in development
54
+ # and staging environments.
55
+ def find_all_redis_keys
56
+ redis.connection do |c|
57
+ c.keys("#{self.REDIS_KEY_NAMESPACE}:*")
58
+ end
59
+ end
60
+
61
+ # Note: this should not be used in production code for performance / memory
62
+ # consumption reasons. This should only be used for debugging in development
63
+ # and staging environments.
64
+ def find_all
65
+ find_all_redis_keys.map do |redis_key|
66
+ find_by_redis_key(redis_key)
67
+ end
68
+ end
69
+ end
70
+
71
+ mixin_instance_methods do
72
+ def initialize(
73
+ identifier: Value.not_given,
74
+ created_at: Value.not_given,
75
+ updated_at: Value.not_given,
76
+ **)
77
+ @identifier = Value.given?(identifier) ? identifier : nil
78
+ @created_at =
79
+ Value.given?(created_at) ? MuchRails::Time.for(created_at) : nil
80
+ @updated_at =
81
+ Value.given?(updated_at) ? MuchRails::Time.for(updated_at) : nil
82
+
83
+ @valid = nil
84
+ end
85
+
86
+ def valid?
87
+ errors.clear
88
+ on_validate
89
+ errors.none?
90
+ end
91
+
92
+ def redis_key
93
+ self.class.redis_key(identifier)
94
+ end
95
+
96
+ def ttl
97
+ redis.connection{ |c| c.ttl(redis_key) }
98
+ end
99
+
100
+ def errors
101
+ @errors ||= HashWithIndifferentAccess.new{ |hash, key| hash[key] = [] }
102
+ end
103
+
104
+ def save!
105
+ validate!
106
+ set_save_transaction_data
107
+
108
+ redis.connection do |c|
109
+ c.multi do
110
+ c.set(
111
+ redis_key,
112
+ MuchRails::JSON.encode(
113
+ to_h.merge({
114
+ "identifier" => identifier,
115
+ "created_at" => created_at.iso8601,
116
+ "updated_at" => updated_at.iso8601,
117
+ }),
118
+ ),
119
+ )
120
+ c.expire(redis_key, self.class.TTL_SECS) if self.class.TTL_SECS
121
+ end
122
+ end
123
+
124
+ true
125
+ end
126
+
127
+ def destroy!
128
+ redis.connection{ |c| c.del(redis_key) } if identifier
129
+
130
+ true
131
+ end
132
+
133
+ def to_h
134
+ raise NotImplementedError
135
+ end
136
+
137
+ def ==(other)
138
+ return super unless other.is_a?(self.class)
139
+
140
+ to_h == other.to_h
141
+ end
142
+
143
+ private
144
+
145
+ def redis
146
+ self.class.redis
147
+ end
148
+
149
+ def set_save_transaction_data
150
+ @identifier ||= SecureRandom.uuid
151
+ @created_at ||= Time.now.utc
152
+ @updated_at = Time.now.utc
153
+ end
154
+
155
+ def validate!
156
+ raise(MuchRails::InvalidError.new(**errors)) unless valid?
157
+ end
158
+
159
+ def on_validate
160
+ end
161
+ end
162
+
163
+ NotFoundError = Class.new(RuntimeError)
164
+
165
+ module Value
166
+ include MuchRails::NotGiven
167
+ end
168
+
169
+ class Config
170
+ attr_accessor :redis
171
+ end
172
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MuchRailsRedisRecord; end
4
+
5
+ module MuchRailsRedisRecord::FakeBehaviors
6
+ include MuchRails::Mixin
7
+
8
+ mixin_instance_methods do
9
+ def initialize(
10
+ identifier: Value.not_given,
11
+ created_at: Value.not_given,
12
+ updated_at: Value.not_given,
13
+ **kargs)
14
+ super(
15
+ identifier: Value.given?(identifier) ? identifier : Factory.uuid,
16
+ created_at: Value.given?(created_at) ? created_at : Factory.time.utc,
17
+ updated_at: Value.given?(updated_at) ? updated_at : Factory.time.utc,
18
+ **kargs,
19
+ )
20
+ end
21
+
22
+ def save_bang_called?
23
+ !!@save_bang_called
24
+ end
25
+
26
+ def destroy_bang_called?
27
+ !!@destroy_bang_called
28
+ end
29
+
30
+ def save!
31
+ validate!
32
+ set_save_transaction_data
33
+
34
+ @save_bang_called = true
35
+
36
+ true
37
+ end
38
+
39
+ def destroy!
40
+ @destroy_bang_called = true if identifier
41
+ end
42
+ end
43
+
44
+ Value = MuchRailsRedisRecord::Value
45
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MuchRailsRedisRecord
4
+ VERSION = "0.1.0"
5
+ end
data/log/.keep ADDED
File without changes
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # frozen_string_literal: true
3
+
4
+ lib = File.expand_path("../lib", __FILE__)
5
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+ require "much-rails-redis-record/version"
7
+
8
+ Gem::Specification.new do |gem|
9
+ gem.name = "much-rails-redis-record"
10
+ gem.version = MuchRailsRedisRecord::VERSION
11
+ gem.authors = ["Kelly Redding", "Collin Redding"]
12
+ gem.email = ["kelly@kellyredding.com", "collin.redding@me.com"]
13
+ gem.summary = "Store records in Redis with MuchRails."
14
+ gem.description = "Store records in Redis with MuchRails."
15
+ gem.homepage = "https://github.com/redding/much-rails-redis-record"
16
+ gem.license = "MIT"
17
+
18
+ gem.files = `git ls-files | grep "^[^.]"`.split($INPUT_RECORD_SEPARATOR)
19
+
20
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
21
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
22
+ gem.require_paths = ["lib"]
23
+
24
+ gem.required_ruby_version = "~> 2.5"
25
+
26
+ gem.add_development_dependency("much-style-guide", ["~> 0.6.4"])
27
+ gem.add_development_dependency("assert", ["~> 2.19.6"])
28
+
29
+ gem.add_dependency("much-rails", ["~> 0.4.2"])
30
+ gem.add_dependency("hella-redis", ["~> 0.5.0"])
31
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ ENV["HELLA_REDIS_TEST_MODE"] = "yes"
4
+
5
+ # Add the root dir to the load path.
6
+ $LOAD_PATH.unshift(File.expand_path("../..", __FILE__))
7
+
8
+ # Require pry for debugging (`binding.pry`).
9
+ require "pry"
10
+
11
+ require "test/support/factory"
12
+ require "much-rails-redis-record"
13
+
14
+ MuchRailsRedisRecord.configure do |config|
15
+ config.redis =
16
+ HellaRedis.new({
17
+ url: ENV.fetch("REDIS_URL"){ "redis://localhost:6379/0" },
18
+ driver: "ruby",
19
+ redis_ns: "much-rails-redis-record:tests",
20
+ size: ENV.fetch("APP_MAX_THREADS"){ 5 },
21
+ timeout: 1,
22
+ })
23
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "assert/factory"
4
+
5
+ module Factory
6
+ extend Assert::Factory
7
+
8
+ def self.uuid
9
+ SecureRandom.uuid
10
+ end
11
+ end
data/test/system/.keep ADDED
File without changes
File without changes
@@ -0,0 +1,397 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "assert"
4
+ require "much-rails-redis-record"
5
+
6
+ module MuchRailsRedisRecord
7
+ class UnitTests < Assert::Context
8
+ desc "MuchRailsRedisRecord"
9
+ subject{ unit_module }
10
+
11
+ let(:unit_module){ MuchRailsRedisRecord }
12
+ end
13
+
14
+ class ReceiverTests < UnitTests
15
+ desc "receiver"
16
+ subject{ receiver_class }
17
+
18
+ setup do
19
+ redis.reset!
20
+
21
+ secure_random_uuid
22
+ Assert.stub(SecureRandom, :uuid){ secure_random_uuid }
23
+ end
24
+
25
+ teardown do
26
+ redis.reset!
27
+ end
28
+
29
+ let(:receiver_class) do
30
+ Class
31
+ .new{
32
+ def self.TTL_SECS
33
+ 1
34
+ end
35
+
36
+ def self.REDIS_KEY_NAMESPACE
37
+ "test:redis-records"
38
+ end
39
+
40
+ attr_reader :field1
41
+
42
+ def initialize(
43
+ field1:,
44
+ **kargs)
45
+ super(**kargs)
46
+
47
+ @field1 = field1.to_s.strip
48
+ end
49
+
50
+ def to_h
51
+ {
52
+ "field1" => field1,
53
+ }
54
+ end
55
+
56
+ private
57
+
58
+ def on_validate
59
+ validate_presence
60
+ end
61
+
62
+ def validate_presence
63
+ errors[:field1] << "can’t be blank" if field1.blank?
64
+ end
65
+ }
66
+ .tap do |c|
67
+ c.include(unit_module)
68
+ end
69
+ end
70
+
71
+ let(:redis){ unit_module.config.redis }
72
+ let(:secure_random_uuid){ Factory.uuid }
73
+
74
+ let(:field1_value){ Factory.string }
75
+ let(:identifier){ secure_random_uuid }
76
+ let(:created_at){ Time.now.utc }
77
+ let(:updated_at){ Time.now.utc }
78
+
79
+ let(:record_data) do
80
+ {
81
+ "field1" => field1_value,
82
+ "identifier" => identifier,
83
+ "created_at" => created_at.iso8601,
84
+ "updated_at" => updated_at.iso8601,
85
+ }
86
+ end
87
+
88
+ should have_imeths :TTL_SECS, :REDIS_KEY_NAMESPACE
89
+ should have_imeths :redis_key, :find_by_identifier, :find_by_redis_key
90
+ should have_imeths :find_all_redis_keys, :find_all
91
+
92
+ should "know its attributes" do
93
+ assert_that(subject.TTL_SECS).equals(1)
94
+ assert_that(subject.REDIS_KEY_NAMESPACE).equals("test:redis-records")
95
+ assert_that(subject.redis_key(secure_random_uuid))
96
+ .equals("#{subject.REDIS_KEY_NAMESPACE}:#{identifier}")
97
+ end
98
+ end
99
+
100
+ class FindByIdentifierSetupTests < ReceiverTests
101
+ desc ".find_by_identifier"
102
+
103
+ setup do
104
+ Assert
105
+ .stub(redis.connection_spy, :get)
106
+ .with(subject.redis_key(identifier)){ encoded_record_data }
107
+ end
108
+
109
+ let(:encoded_record_data){ MuchRails::JSON.encode(record_data) }
110
+ end
111
+
112
+ class FindByExistingIdentifierTests < FindByIdentifierSetupTests
113
+ desc "with an existing identifier"
114
+
115
+ setup do
116
+ Assert
117
+ .stub(redis.connection_spy, :exists?)
118
+ .with(subject.redis_key(identifier)){ true }
119
+ end
120
+
121
+ should "lookup the existing record data and build an instance with it" do
122
+ record = subject.find_by_identifier(identifier)
123
+ assert_that(record).equals(subject.new(**record_data.symbolize_keys))
124
+ end
125
+ end
126
+
127
+ class FindByNonExistingIdentifierTests < FindByIdentifierSetupTests
128
+ desc "with an non-existing identifier"
129
+
130
+ setup do
131
+ Assert
132
+ .stub(redis.connection_spy, :exists?)
133
+ .with(subject.redis_key(identifier)){ false }
134
+ end
135
+
136
+ should "raise an exception" do
137
+ assert_that{ subject.find_by_identifier(identifier) }
138
+ .raises(subject::NotFoundError)
139
+ end
140
+ end
141
+
142
+ class FindByRedisKeySetupTests < ReceiverTests
143
+ desc ".find_by_redis_key"
144
+
145
+ setup do
146
+ Assert
147
+ .stub(redis.connection_spy, :get)
148
+ .with(subject.redis_key(identifier)){ encoded_record_data }
149
+ end
150
+
151
+ let(:encoded_record_data){ MuchRails::JSON.encode(record_data) }
152
+ end
153
+
154
+ class FindByExistingRedisKeyTests < FindByRedisKeySetupTests
155
+ desc "with an existing redis key"
156
+
157
+ setup do
158
+ Assert
159
+ .stub(redis.connection_spy, :exists?)
160
+ .with(subject.redis_key(identifier)){ true }
161
+ end
162
+
163
+ should "lookup the existing record data and build an instance with it" do
164
+ record = subject.find_by_redis_key(subject.redis_key(identifier))
165
+ assert_that(record).equals(subject.new(**record_data.symbolize_keys))
166
+ end
167
+ end
168
+
169
+ class FindByNonExistingRedisKeyTests < FindByRedisKeySetupTests
170
+ desc "with an non-existing redis key"
171
+
172
+ setup do
173
+ Assert
174
+ .stub(redis.connection_spy, :exists?)
175
+ .with(subject.redis_key(identifier)){ false }
176
+ end
177
+
178
+ should "raise an exception" do
179
+ assert_that{ subject.find_by_redis_key(subject.redis_key(identifier)) }
180
+ .raises(subject::NotFoundError)
181
+ end
182
+ end
183
+
184
+ class FindAllRedisKeysTests < ReceiverTests
185
+ desc ".find_all_redis_keys"
186
+
187
+ setup do
188
+ Assert
189
+ .stub(redis.connection_spy, :keys)
190
+ .with("#{receiver_class.REDIS_KEY_NAMESPACE}:*"){ all_redis_keys }
191
+ end
192
+
193
+ let(:all_redis_keys) do
194
+ Array.new(Factory.integer(3)) do
195
+ "#{receiver_class.REDIS_KEY_NAMESPACE}:#{Factory.uuid}"
196
+ end
197
+ end
198
+
199
+ should "lookup all redis keys matching the key namespace" do
200
+ assert_that(subject.find_all_redis_keys).equals(all_redis_keys)
201
+ end
202
+ end
203
+
204
+ class FindAllTests < ReceiverTests
205
+ desc ".find_all_tests"
206
+
207
+ setup do
208
+ Assert.stub(receiver_class, :find_all_redis_keys) do
209
+ redis_record_redis_keys
210
+ end
211
+ Assert
212
+ .stub(receiver_class, :find_by_redis_key)
213
+ .with(redis_record_redis_keys.first) do
214
+ redis_records.first
215
+ end
216
+ end
217
+
218
+ let(:redis_records) do
219
+ [receiver_class.new(**record_data.symbolize_keys)]
220
+ end
221
+ let(:redis_record_redis_keys){ redis_records.map(&:redis_key) }
222
+
223
+ should "lookup all redis keys matching the key namespace" do
224
+ assert_that(subject.find_all).equals(redis_records)
225
+ end
226
+ end
227
+
228
+ class InitTests < ReceiverTests
229
+ desc "when init"
230
+ subject{ receiver_class.new(field1: field1_value) }
231
+
232
+ should have_imeths :valid?, :errors, :save!, :destroy!, :to_h
233
+
234
+ should "know its attributes" do
235
+ assert_that(subject.valid?).equals(true)
236
+ assert_that(subject.errors).equals({})
237
+ assert_that(subject.to_h)
238
+ .equals({
239
+ "field1" => field1_value,
240
+ })
241
+ end
242
+ end
243
+
244
+ class SaveSetupTests < InitTests
245
+ desc ".save!"
246
+
247
+ setup do
248
+ create_time_now
249
+ Assert.stub(Time, :now){ create_time_now }
250
+ end
251
+
252
+ let(:create_time_now){ Time.now }
253
+ end
254
+
255
+ class SaveNewRecordTests < SaveSetupTests
256
+ desc "on a new record"
257
+
258
+ should "set save data and save the record" do
259
+ assert_that(subject.identifier).is_nil
260
+ assert_that(subject.created_at).is_nil
261
+ assert_that(subject.updated_at).is_nil
262
+
263
+ result = subject.save!
264
+ assert_that(result).is_true
265
+
266
+ assert_that(subject.identifier).equals(identifier)
267
+ assert_that(subject.created_at).equals(create_time_now)
268
+ assert_that(subject.updated_at).equals(create_time_now)
269
+
270
+ assert_that(redis.calls.size).equals(3)
271
+ multi_call, set_call, expire_call = redis.calls
272
+
273
+ assert_that(multi_call.command).equals(:multi)
274
+
275
+ assert_that(set_call.command).equals(:set)
276
+ assert_that(set_call.args)
277
+ .equals([
278
+ subject.class.redis_key(identifier),
279
+ MuchRails::JSON.encode(
280
+ subject.to_h.merge({
281
+ "identifier" => subject.identifier,
282
+ "created_at" => subject.created_at.iso8601,
283
+ "updated_at" => subject.updated_at.iso8601,
284
+ }),
285
+ ),
286
+ ])
287
+
288
+ assert_that(expire_call.command).equals(:expire)
289
+ assert_that(expire_call.args)
290
+ .equals([subject.class.redis_key(identifier), subject.class.TTL_SECS])
291
+ end
292
+ end
293
+
294
+ class SaveExistingRecordTests < SaveSetupTests
295
+ desc "on an existing record"
296
+
297
+ setup do
298
+ subject.save!
299
+
300
+ Assert.unstub(Time, :now)
301
+ update_time_now
302
+ Assert.stub(Time, :now){ update_time_now }
303
+
304
+ redis.reset!
305
+ end
306
+
307
+ let(:update_time_now){ Time.now }
308
+
309
+ should "set save data and save the record" do
310
+ assert_that(subject.identifier).equals(identifier)
311
+ assert_that(subject.created_at).equals(create_time_now)
312
+ assert_that(subject.updated_at).equals(create_time_now)
313
+
314
+ result = subject.save!
315
+ assert_that(result).is_true
316
+
317
+ assert_that(subject.identifier).equals(identifier)
318
+ assert_that(subject.created_at).equals(create_time_now)
319
+ assert_that(subject.updated_at).equals(update_time_now)
320
+ end
321
+ end
322
+
323
+ class SaveWithNoTTLTests < SaveSetupTests
324
+ desc "with no TTL_SECS"
325
+
326
+ setup do
327
+ Assert.stub(receiver_class, :TTL_SECS){ nil }
328
+ end
329
+
330
+ should "set save data and save the record" do
331
+ subject.save!
332
+
333
+ assert_that(redis.calls.size).equals(2)
334
+ multi_call, set_call = redis.calls
335
+
336
+ assert_that(multi_call.command).equals(:multi)
337
+ assert_that(set_call.command).equals(:set)
338
+ end
339
+ end
340
+
341
+ class SaveWithValidationErrorsTests < SaveSetupTests
342
+ desc "with validation errors"
343
+
344
+ setup do
345
+ Assert.stub(subject, :field1){ [nil, ""].sample }
346
+ end
347
+
348
+ should "not set save data and not save the record" do
349
+ assert_that(subject.valid?).equals(false)
350
+ exception =
351
+ assert_that{ subject.save! }.raises(MuchRails::InvalidError)
352
+
353
+ assert_that(exception.errors).equals(subject.errors)
354
+ assert_that(redis.calls.size).equals(0)
355
+ end
356
+ end
357
+
358
+ class DestroySetupTests < InitTests
359
+ desc ".destroy!"
360
+ end
361
+
362
+ class DestroyNewRecordTests < DestroySetupTests
363
+ desc "on a new record"
364
+
365
+ should "do nothing in Redis" do
366
+ assert_that(subject.identifier).is_nil
367
+
368
+ result = subject.destroy!
369
+ assert_that(result).is_true
370
+
371
+ assert_that(redis.calls.size).equals(0)
372
+ end
373
+ end
374
+
375
+ class DestroyExistingRecordTests < DestroySetupTests
376
+ desc "on an existing record"
377
+
378
+ setup do
379
+ subject.save!
380
+ redis.reset!
381
+ end
382
+
383
+ should "delete the record in Redis" do
384
+ assert_that(subject.identifier).is_not_nil
385
+
386
+ result = subject.destroy!
387
+ assert_that(result).is_true
388
+
389
+ assert_that(redis.calls.size).equals(1)
390
+ delete_call = redis.calls.last
391
+
392
+ assert_that(delete_call.command).equals(:del)
393
+ assert_that(delete_call.args)
394
+ .equals([subject.class.redis_key(identifier)])
395
+ end
396
+ end
397
+ end
data/tmp/.keep ADDED
File without changes
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: much-rails-redis-record
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kelly Redding
8
+ - Collin Redding
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2021-06-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: much-style-guide
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: 0.6.4
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: 0.6.4
28
+ - !ruby/object:Gem::Dependency
29
+ name: assert
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: 2.19.6
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: 2.19.6
42
+ - !ruby/object:Gem::Dependency
43
+ name: much-rails
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: 0.4.2
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: 0.4.2
56
+ - !ruby/object:Gem::Dependency
57
+ name: hella-redis
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: 0.5.0
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 0.5.0
70
+ description: Store records in Redis with MuchRails.
71
+ email:
72
+ - kelly@kellyredding.com
73
+ - collin.redding@me.com
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - Gemfile
79
+ - LICENSE
80
+ - README.md
81
+ - lib/much-rails-redis-record.rb
82
+ - lib/much-rails-redis-record/fake_behaviors.rb
83
+ - lib/much-rails-redis-record/version.rb
84
+ - log/.keep
85
+ - much-rails-redis-record.gemspec
86
+ - test/helper.rb
87
+ - test/support/factory.rb
88
+ - test/system/.keep
89
+ - test/unit/fake_behaviors_tests.rb
90
+ - test/unit/much-rails-redis-record_tests.rb
91
+ - tmp/.keep
92
+ homepage: https://github.com/redding/much-rails-redis-record
93
+ licenses:
94
+ - MIT
95
+ metadata: {}
96
+ post_install_message:
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: '2.5'
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubygems_version: 3.1.6
112
+ signing_key:
113
+ specification_version: 4
114
+ summary: Store records in Redis with MuchRails.
115
+ test_files:
116
+ - test/helper.rb
117
+ - test/support/factory.rb
118
+ - test/system/.keep
119
+ - test/unit/fake_behaviors_tests.rb
120
+ - test/unit/much-rails-redis-record_tests.rb