Skip to content

Commit cc15c2f

Browse files
authored
Default values on initialization (#119)
1 parent 916f6ef commit cc15c2f

22 files changed

+557
-66
lines changed

README.md

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ list = Kredis.list "mylist"
4949
list << "hello world!" # => RPUSH mylist "hello world!"
5050
[ "hello world!" ] == list.elements # => LRANGE mylist 0, -1
5151

52-
integer_list = Kredis.list "myintegerlist", typed: :integer
53-
integer_list.append([ 1, 2, 3 ]) # => RPUSH myintegerlist "1" "2" "3"
54-
integer_list << 4 # => RPUSH myintegerlist "4"
55-
[ 1, 2, 3, 4 ] == integer_list.elements # => LRANGE myintegerlist 0 -1
52+
integer_list = Kredis.list "myintegerlist", typed: :integer, default: [ 1, 2, 3 ] # => EXISTS? myintegerlist, RPUSH myintegerlist "1" "2" "3"
53+
integer_list.append([ 4, 5, 6 ]) # => RPUSH myintegerlist "4" "5" "6"
54+
integer_list << 7 # => RPUSH myintegerlist "7"
55+
[ 1, 2, 3, 4, 5, 6, 7 ] == integer_list.elements # => LRANGE myintegerlist 0 -1
5656

5757
unique_list = Kredis.unique_list "myuniquelist"
5858
unique_list.append(%w[ 2 3 4 ]) # => LREM myuniquelist 0, "2" + LREM myuniquelist 0, "3" + LREM myuniquelist 0, "4" + RPUSH myuniquelist "2", "3", "4"
@@ -163,15 +163,7 @@ sleep 0.6.seconds
163163
false == flag.marked? #=> EXISTS myflag
164164
```
165165

166-
And using structures on a different than the default `shared` redis instance, relying on `config/redis/secondary.yml`:
167-
168-
```ruby
169-
one_string = Kredis.string "mystring"
170-
two_string = Kredis.string "mystring", config: :secondary
171-
172-
one_string.value = "just on shared"
173-
two_string.value != one_string.value
174-
```
166+
### Models
175167

176168
You can use all these structures in models:
177169

@@ -197,6 +189,29 @@ person.morning.value = "blue" # => SET people:5:morning
197189
true == person.morning.blue? # => GET people:5:morning
198190
```
199191

192+
### Default values
193+
194+
You can set a default value for all types. For example:
195+
196+
```ruby
197+
list = Kredis.list "favorite_colors", default: [ "red", "green", "blue" ]
198+
199+
# or, in a model
200+
class Person < ApplicationRecord
201+
kredis_string :name, default: "Unknown"
202+
kredis_list :favorite_colors, default: [ "red", "green", "blue" ]
203+
end
204+
```
205+
206+
There's a performance overhead to consider though. When you first read or write an attribute in a model, Kredis will
207+
check if the underlying Redis key exists, while watching for concurrent changes, and if it does not,
208+
write the specified default value.
209+
210+
This means that using default values in a typical Rails app additional Redis calls (WATCH, EXISTS, UNWATCH) will be
211+
executed for each Kredis attribute with a default value read or written during a request.
212+
213+
### Callbacks
214+
200215
You can also define `after_change` callbacks that trigger on mutations:
201216

202217
```ruby
@@ -209,6 +224,18 @@ class Person < ApplicationRecord
209224
end
210225
```
211226

227+
### Multiple Redis servers
228+
229+
And using structures on a different than the default `shared` redis instance, relying on `config/redis/secondary.yml`:
230+
231+
```ruby
232+
one_string = Kredis.string "mystring"
233+
two_string = Kredis.string "mystring", config: :secondary
234+
235+
one_string.value = "just on shared"
236+
two_string.value != one_string.value
237+
```
238+
212239
## Installation
213240

214241
1. Run `./bin/bundle add kredis`

lib/kredis.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
require "kredis/log_subscriber"
1111
require "kredis/namespace"
1212
require "kredis/type_casting"
13+
require "kredis/default_values"
1314
require "kredis/types"
1415
require "kredis/attributes"
1516

lib/kredis/attributes.rb

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,56 +8,56 @@ def kredis_proxy(name, key: nil, config: :shared, after_change: nil)
88
kredis_connection_with __method__, name, key, config: config, after_change: after_change
99
end
1010

11-
def kredis_string(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
12-
kredis_connection_with __method__, name, key, config: config, after_change: after_change, expires_in: expires_in
11+
def kredis_string(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil)
12+
kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in
1313
end
1414

15-
def kredis_integer(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
16-
kredis_connection_with __method__, name, key, config: config, after_change: after_change, expires_in: expires_in
15+
def kredis_integer(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil)
16+
kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in
1717
end
1818

19-
def kredis_decimal(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
20-
kredis_connection_with __method__, name, key, config: config, after_change: after_change, expires_in: expires_in
19+
def kredis_decimal(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil)
20+
kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in
2121
end
2222

23-
def kredis_datetime(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
24-
kredis_connection_with __method__, name, key, config: config, after_change: after_change, expires_in: expires_in
23+
def kredis_datetime(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil)
24+
kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in
2525
end
2626

27-
def kredis_flag(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
28-
kredis_connection_with __method__, name, key, config: config, after_change: after_change, expires_in: expires_in
27+
def kredis_flag(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil)
28+
kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in
2929

3030
define_method("#{name}?") do
3131
send(name).marked?
3232
end
3333
end
3434

35-
def kredis_float(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
36-
kredis_connection_with __method__, name, key, config: config, after_change: after_change, expires_in: expires_in
35+
def kredis_float(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil)
36+
kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in
3737
end
3838

3939
def kredis_enum(name, key: nil, values:, default:, config: :shared, after_change: nil)
4040
kredis_connection_with __method__, name, key, values: values, default: default, config: config, after_change: after_change
4141
end
4242

43-
def kredis_json(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
44-
kredis_connection_with __method__, name, key, config: config, after_change: after_change, expires_in: expires_in
43+
def kredis_json(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil)
44+
kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in
4545
end
4646

47-
def kredis_list(name, key: nil, typed: :string, config: :shared, after_change: nil)
48-
kredis_connection_with __method__, name, key, typed: typed, config: config, after_change: after_change
47+
def kredis_list(name, key: nil, default: nil, typed: :string, config: :shared, after_change: nil)
48+
kredis_connection_with __method__, name, key, default: default, typed: typed, config: config, after_change: after_change
4949
end
5050

51-
def kredis_unique_list(name, limit: nil, key: nil, typed: :string, config: :shared, after_change: nil)
52-
kredis_connection_with __method__, name, key, limit: limit, typed: typed, config: config, after_change: after_change
51+
def kredis_unique_list(name, limit: nil, key: nil, default: nil, typed: :string, config: :shared, after_change: nil)
52+
kredis_connection_with __method__, name, key, default: default, limit: limit, typed: typed, config: config, after_change: after_change
5353
end
5454

55-
def kredis_ordered_set(name, limit: nil, key: nil, typed: :string, config: :shared, after_change: nil)
56-
kredis_connection_with __method__, name, key, limit: limit, typed: typed, config: config, after_change: after_change
55+
def kredis_set(name, key: nil, default: nil, typed: :string, config: :shared, after_change: nil)
56+
kredis_connection_with __method__, name, key, default: default, typed: typed, config: config, after_change: after_change
5757
end
5858

59-
def kredis_set(name, key: nil, typed: :string, config: :shared, after_change: nil)
60-
kredis_connection_with __method__, name, key, typed: typed, config: config, after_change: after_change
59+
def kredis_ordered_set(name, limit: nil, default: nil, key: nil, typed: :string, config: :shared, after_change: nil)
60+
kredis_connection_with __method__, name, key, default: default, limit: limit, typed: typed, config: config, after_change: after_change
6161
end
6262

6363
def kredis_slot(name, key: nil, config: :shared, after_change: nil)
@@ -68,16 +68,16 @@ def kredis_slots(name, available:, key: nil, config: :shared, after_change: nil)
6868
kredis_connection_with __method__, name, key, available: available, config: config, after_change: after_change
6969
end
7070

71-
def kredis_counter(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
72-
kredis_connection_with __method__, name, key, config: config, after_change: after_change, expires_in: expires_in
71+
def kredis_counter(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil)
72+
kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in
7373
end
7474

75-
def kredis_hash(name, key: nil, typed: :string, config: :shared, after_change: nil)
76-
kredis_connection_with __method__, name, key, typed: typed, config: config, after_change: after_change
75+
def kredis_hash(name, key: nil, default: nil, typed: :string, config: :shared, after_change: nil)
76+
kredis_connection_with __method__, name, key, default: default, typed: typed, config: config, after_change: after_change
7777
end
7878

79-
def kredis_boolean(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
80-
kredis_connection_with __method__, name, key, config: config, after_change: after_change, expires_in: expires_in
79+
def kredis_boolean(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil)
80+
kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in
8181
end
8282

8383
private
@@ -90,6 +90,7 @@ def kredis_connection_with(method, name, key, **options)
9090
if instance_variable_defined?(ivar_symbol)
9191
instance_variable_get(ivar_symbol)
9292
else
93+
options[:default] = kredis_default_evaluated(options[:default]) if options[:default]
9394
new_type = Kredis.send(type, kredis_key_evaluated(key) || kredis_key_for_attribute(name), **options)
9495
instance_variable_set ivar_symbol,
9596
after_change ? enrich_after_change_with_record_access(new_type, after_change) : new_type
@@ -121,4 +122,12 @@ def enrich_after_change_with_record_access(type, original_after_change)
121122
when Symbol then Kredis::Types::CallbacksProxy.new(type, ->(_) { send(original_after_change) })
122123
end
123124
end
125+
126+
def kredis_default_evaluated(default)
127+
case default
128+
when Proc then Proc.new { default.call(self) }
129+
when Symbol then send(default)
130+
else default
131+
end
132+
end
124133
end

lib/kredis/default_values.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# frozen_string_literal: true
2+
3+
module Kredis::DefaultValues
4+
extend ActiveSupport::Concern
5+
6+
prepended do
7+
attr_writer :default
8+
9+
proxying :watch, :unwatch, :exists?
10+
11+
def default
12+
case @default
13+
when Proc then @default.call
14+
when Symbol then send(@default)
15+
else @default
16+
end
17+
end
18+
19+
private
20+
def set_default
21+
raise NotImplementedError, "Kredis type #{self.class} needs to define #set_default"
22+
end
23+
end
24+
25+
def initialize(...)
26+
super
27+
28+
if default
29+
watch do
30+
set_default unless exists?
31+
32+
unwatch
33+
end
34+
end
35+
end
36+
end

lib/kredis/types.rb

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,40 +41,40 @@ def json(key, default: nil, config: :shared, after_change: nil, expires_in: nil)
4141
end
4242

4343

44-
def counter(key, expires_in: nil, config: :shared, after_change: nil)
45-
type_from(Counter, config, key, after_change: after_change, expires_in: expires_in)
44+
def counter(key, expires_in: nil, default: nil, config: :shared, after_change: nil)
45+
type_from(Counter, config, key, after_change: after_change, default: default, expires_in: expires_in)
4646
end
4747

4848
def cycle(key, values:, expires_in: nil, config: :shared, after_change: nil)
4949
type_from(Cycle, config, key, after_change: after_change, values: values, expires_in: expires_in)
5050
end
5151

52-
def flag(key, config: :shared, after_change: nil, expires_in: nil)
53-
type_from(Flag, config, key, after_change: after_change, expires_in: expires_in)
52+
def flag(key, default: nil, config: :shared, after_change: nil, expires_in: nil)
53+
type_from(Flag, config, key, after_change: after_change, default: default, expires_in: expires_in)
5454
end
5555

5656
def enum(key, values:, default:, config: :shared, after_change: nil)
5757
type_from(Enum, config, key, after_change: after_change, values: values, default: default)
5858
end
5959

60-
def hash(key, typed: :string, config: :shared, after_change: nil)
61-
type_from(Hash, config, key, after_change: after_change, typed: typed)
60+
def hash(key, typed: :string, default: nil, config: :shared, after_change: nil)
61+
type_from(Hash, config, key, after_change: after_change, default: default, typed: typed)
6262
end
6363

64-
def list(key, typed: :string, config: :shared, after_change: nil)
65-
type_from(List, config, key, after_change: after_change, typed: typed)
64+
def list(key, default: nil, typed: :string, config: :shared, after_change: nil)
65+
type_from(List, config, key, after_change: after_change, default: default, typed: typed)
6666
end
6767

68-
def unique_list(key, typed: :string, limit: nil, config: :shared, after_change: nil)
69-
type_from(UniqueList, config, key, after_change: after_change, typed: typed, limit: limit)
68+
def unique_list(key, default: nil, typed: :string, limit: nil, config: :shared, after_change: nil)
69+
type_from(UniqueList, config, key, after_change: after_change, default: default, typed: typed, limit: limit)
7070
end
7171

72-
def set(key, typed: :string, config: :shared, after_change: nil)
73-
type_from(Set, config, key, after_change: after_change, typed: typed)
72+
def set(key, default: nil, typed: :string, config: :shared, after_change: nil)
73+
type_from(Set, config, key, after_change: after_change, default: default, typed: typed)
7474
end
7575

76-
def ordered_set(key, typed: :string, limit: nil, config: :shared, after_change: nil)
77-
type_from(OrderedSet, config, key, after_change: after_change, typed: typed, limit: limit)
76+
def ordered_set(key, default: nil, typed: :string, limit: nil, config: :shared, after_change: nil)
77+
type_from(OrderedSet, config, key, after_change: after_change, default: default, typed: typed, limit: limit)
7878
end
7979

8080
def slot(key, config: :shared, after_change: nil)

lib/kredis/types/counter.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# frozen_string_literal: true
22

33
class Kredis::Types::Counter < Kredis::Types::Proxying
4+
prepend Kredis::DefaultValues
5+
46
proxying :multi, :set, :incrby, :decrby, :get, :del, :exists?
57

68
attr_accessor :expires_in
@@ -26,4 +28,9 @@ def value
2628
def reset
2729
del
2830
end
31+
32+
private
33+
def set_default
34+
increment by: default
35+
end
2936
end

lib/kredis/types/enum.rb

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@
33
require "active_support/core_ext/object/inclusion"
44

55
class Kredis::Types::Enum < Kredis::Types::Proxying
6-
proxying :set, :get, :del, :exists?
6+
prepend Kredis::DefaultValues
77

8-
attr_accessor :values, :default
8+
InvalidDefault = Class.new(StandardError)
9+
10+
proxying :set, :get, :del, :exists?, :multi
11+
12+
attr_accessor :values
913

1014
def initialize(...)
1115
super
@@ -19,11 +23,14 @@ def value=(value)
1923
end
2024

2125
def value
22-
get || default
26+
get
2327
end
2428

2529
def reset
26-
del
30+
multi do
31+
del
32+
set_default
33+
end
2734
end
2835

2936
private
@@ -33,4 +40,12 @@ def define_predicates_for_values
3340
define_singleton_method("#{defined_value}!") { self.value = defined_value }
3441
end
3542
end
43+
44+
def set_default
45+
if default.in?(values) || default.nil?
46+
set default
47+
else
48+
raise InvalidDefault, "Default value #{default.inspect} for #{key} is not a valid option (Valid values: #{values.join(", ")})"
49+
end
50+
end
3651
end

lib/kredis/types/flag.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# frozen_string_literal: true
22

33
class Kredis::Types::Flag < Kredis::Types::Proxying
4+
prepend Kredis::DefaultValues
5+
46
proxying :set, :exists?, :del
57

68
attr_accessor :expires_in
@@ -16,4 +18,9 @@ def marked?
1618
def remove
1719
del
1820
end
21+
22+
private
23+
def set_default
24+
mark if default
25+
end
1926
end

0 commit comments

Comments
 (0)