much-rails-redis-record 0.1.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 +7 -0
- data/Gemfile +9 -0
- data/LICENSE +22 -0
- data/README.md +165 -0
- data/lib/much-rails-redis-record.rb +172 -0
- data/lib/much-rails-redis-record/fake_behaviors.rb +45 -0
- data/lib/much-rails-redis-record/version.rb +5 -0
- data/log/.keep +0 -0
- data/much-rails-redis-record.gemspec +31 -0
- data/test/helper.rb +23 -0
- data/test/support/factory.rb +11 -0
- data/test/system/.keep +0 -0
- data/test/unit/fake_behaviors_tests.rb +0 -0
- data/test/unit/much-rails-redis-record_tests.rb +397 -0
- data/tmp/.keep +0 -0
- metadata +120 -0
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
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
|
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
|
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
|