Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Callbacks proxy #35

Merged
merged 12 commits into from
Jul 3, 2021
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,17 @@ person.morning.value = "blue" # => SET people:1:morning
true == person.morning.blue? # => GET people:1:morning
```

You can also define `after_change` callbacks that trigger on mutations:

```ruby
class Person < ApplicationRecord
kredis_list :names, after_change: ->(p) { }
kredis_unique_list :skills, limit: 2, after_change: :skillset_changed

def skillset_changed(p)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think the instance method callback should probably take a parameter, yeah? Like they don't in Active Record/Model. Only the procs do that.

end
end
```

## Installation

Expand Down
1 change: 1 addition & 0 deletions lib/kredis.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
require "kredis/type_casting"
require "kredis/types"
require "kredis/attributes"
require "kredis/callbacks_proxy"

require "kredis/railtie" if defined?(Rails::Railtie)

Expand Down
64 changes: 35 additions & 29 deletions lib/kredis/attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,76 +2,82 @@ module Kredis::Attributes
extend ActiveSupport::Concern

class_methods do
def kredis_proxy(name, key: nil, config: :shared)
kredis_connection_with __method__, name, key, config: config
def kredis_proxy(name, key: nil, config: :shared, after_change: nil)
kredis_connection_with __method__, name, key, config: config, after_change: after_change
end

def kredis_string(name, key: nil, config: :shared)
kredis_connection_with __method__, name, key, config: config
def kredis_string(name, key: nil, config: :shared, after_change: nil)
kredis_connection_with __method__, name, key, config: config, after_change: after_change
end

def kredis_integer(name, key: nil, config: :shared)
kredis_connection_with __method__, name, key, config: config
def kredis_integer(name, key: nil, config: :shared, after_change: nil)
kredis_connection_with __method__, name, key, config: config, after_change: after_change
end

def kredis_decimal(name, key: nil, config: :shared)
kredis_connection_with __method__, name, key, config: config
def kredis_decimal(name, key: nil, config: :shared, after_change: nil)
kredis_connection_with __method__, name, key, config: config, after_change: after_change
end

def kredis_datetime(name, key: nil, config: :shared)
kredis_connection_with __method__, name, key, config: config
def kredis_datetime(name, key: nil, config: :shared, after_change: nil)
kredis_connection_with __method__, name, key, config: config, after_change: after_change
end

def kredis_flag(name, key: nil, config: :shared)
kredis_connection_with __method__, name, key, config: config
def kredis_flag(name, key: nil, config: :shared, after_change: nil)
kredis_connection_with __method__, name, key, config: config, after_change: after_change

define_method("#{name}?") do
send(name).marked?
end
end

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

def kredis_json(name, key: nil, config: :shared)
kredis_connection_with __method__, name, key, config: config
def kredis_json(name, key: nil, config: :shared, after_change: nil)
kredis_connection_with __method__, name, key, config: config, after_change: after_change
end

def kredis_list(name, key: nil, typed: :string, config: :shared)
kredis_connection_with __method__, name, key, typed: typed, config: config
def kredis_list(name, key: nil, typed: :string, config: :shared, after_change: nil)
kredis_connection_with __method__, name, key, typed: typed, config: config, after_change: after_change
end

def kredis_unique_list(name, limit: nil, key: nil, typed: :string, config: :shared)
kredis_connection_with __method__, name, key, limit: limit, typed: typed, config: config
def kredis_unique_list(name, limit: nil, key: nil, typed: :string, config: :shared, after_change: nil)
kredis_connection_with __method__, name, key, limit: limit, typed: typed, config: config, after_change: after_change
end

def kredis_set(name, key: nil, typed: :string, config: :shared)
kredis_connection_with __method__, name, key, typed: typed, config: config
def kredis_set(name, key: nil, typed: :string, config: :shared, after_change: nil)
kredis_connection_with __method__, name, key, typed: typed, config: config, after_change: after_change
end

def kredis_slot(name, key: nil, config: :shared)
kredis_connection_with __method__, name, key, config: config
def kredis_slot(name, key: nil, config: :shared, after_change: nil)
kredis_connection_with __method__, name, key, config: config, after_change: after_change
end

def kredis_slots(name, available:, key: nil, config: :shared)
kredis_connection_with __method__, name, key, available: available, config: config
def kredis_slots(name, available:, key: nil, config: :shared, after_change: nil)
kredis_connection_with __method__, name, key, available: available, config: config, after_change: after_change
end

def kredis_counter(name, key: nil, config: :shared)
kredis_connection_with __method__, name, key, config: config
def kredis_counter(name, key: nil, config: :shared, after_change: nil)
kredis_connection_with __method__, name, key, config: config, after_change: after_change
end

private
def kredis_connection_with(method, name, key, **options)
ivar_symbol = :"@#{name}_#{method}"
type = method.to_s.sub("kredis_", "")
callback = options.delete(:after_change)

define_method(name) do
if instance_variable_defined?(ivar_symbol)
instance_variable_get(ivar_symbol)
else
instance_variable_set(ivar_symbol, Kredis.send(type, kredis_key_evaluated(key) || kredis_key_for_attribute(name), **options))
instance_variable_set(ivar_symbol,
Kredis::CallbacksProxy.new(
Kredis.send(type, kredis_key_evaluated(key) || kredis_key_for_attribute(name), **options),
self,
callback)
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think assigning the proxy to an explaining variable could help with the multi-line nature here.

end
end
end
Expand Down
22 changes: 22 additions & 0 deletions lib/kredis/callbacks_proxy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class Kredis::CallbacksProxy
attr_reader :type
delegate :to_s, to: :type

def initialize(type, record, callback)
@type, @record, @callback = type, record, callback
end

def method_missing(method, *args, **kwargs, &block)
result = @type.send(method, *args, **kwargs, &block)

if @type.callback_operations&.include? method
if @callback.respond_to? :call
@callback.call(@record, @type)
elsif @callback.is_a? Symbol
@record.send(@callback, @record, @type)
end
end

result
end
end
4 changes: 4 additions & 0 deletions lib/kredis/types/counter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ class Kredis::Types::Counter < Kredis::Types::Proxying

attr_accessor :expires_in

def callback_operations
%i[increment decrement reset].freeze
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like a configuration-level method that shouldn't be exposed directly through the API. What about using a constant instead?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then you also don't have a to call freeze. And for style, space after opening and before closing 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm too used to standard 😅


def increment(by: 1)
multi do
set 0, ex: expires_in, nx: true
Expand Down
4 changes: 4 additions & 0 deletions lib/kredis/types/cycle.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
class Kredis::Types::Cycle < Kredis::Types::Counter
attr_accessor :values

def callback_operations
%i[next].freeze
end

alias index value

def value
Expand Down
4 changes: 4 additions & 0 deletions lib/kredis/types/enum.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ class Kredis::Types::Enum < Kredis::Types::Proxying

attr_accessor :values, :default

def callback_operations
%i[value= reset].freeze
end

def initialize(...)
super
define_predicates_for_values
Expand Down
4 changes: 4 additions & 0 deletions lib/kredis/types/flag.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
class Kredis::Types::Flag < Kredis::Types::Proxying
proxying :set, :exists?, :del

def callback_operations
%i[mark remove].freeze
end

def mark(expires_in: nil)
set 1, ex: expires_in
end
Expand Down
4 changes: 4 additions & 0 deletions lib/kredis/types/list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ class Kredis::Types::List < Kredis::Types::Proxying

attr_accessor :typed

def callback_operations
%i[remove prepend append].freeze
end

def elements
strings_to_types(lrange(0, -1) || [], typed)
end
Expand Down
4 changes: 4 additions & 0 deletions lib/kredis/types/scalar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ class Kredis::Types::Scalar < Kredis::Types::Proxying

attr_accessor :typed, :default

def callback_operations
%i[value= clear].freeze
end

def value=(value)
set type_to_string(value)
end
Expand Down
4 changes: 4 additions & 0 deletions lib/kredis/types/set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ class Kredis::Types::Set < Kredis::Types::Proxying

attr_accessor :typed

def callback_operations
%i[add remove replace take clear].freeze
end

def members
strings_to_types(smembers || [], typed).sort
end
Expand Down
4 changes: 4 additions & 0 deletions lib/kredis/types/slots.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ class NotAvailable < StandardError; end

attr_accessor :available

def callback_operations
%i[reserve release reset].freeze
end

def reserve
failsafe returning: false do
if block_given?
Expand Down
Loading