diff --git a/README.md b/README.md index d328ad9..332feae 100644 --- a/README.md +++ b/README.md @@ -172,10 +172,12 @@ class Person < ApplicationRecord kredis_list :names kredis_list :names_with_custom_key_via_lambda, key: ->(p) { "person:#{p.id}:names_customized" } kredis_list :names_with_custom_key_via_method, key: :generate_names_key - kredis_unique_list :skills, limit: 2 + kredis_unique_list :skills, limit: 2, scope: :user # stored at users:1:person:skills kredis_enum :morning, values: %w[ bright blue black ], default: "bright" kredis_counter :steps, expires_in: 1.hour + belongs_to :user + private def generate_names_key "key-generated-from-private-method" diff --git a/lib/kredis/attributes.rb b/lib/kredis/attributes.rb index b844501..d1ebd28 100644 --- a/lib/kredis/attributes.rb +++ b/lib/kredis/attributes.rb @@ -4,94 +4,96 @@ module Kredis::Attributes extend ActiveSupport::Concern class_methods do - def kredis_proxy(name, key: nil, config: :shared, after_change: nil) - kredis_connection_with __method__, name, key, config: config, after_change: after_change + def kredis_proxy(name, key: nil, scope: nil, config: :shared, after_change: nil) + kredis_connection_with __method__, name, key, scope: scope, config: config, after_change: after_change end - def kredis_string(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil) - kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in + def kredis_string(name, key: nil, scope: nil, default: nil, config: :shared, after_change: nil, expires_in: nil) + kredis_connection_with __method__, name, key, scope: scope, default: default, config: config, after_change: after_change, expires_in: expires_in end - def kredis_integer(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil) - kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in + def kredis_integer(name, key: nil, scope: nil, default: nil, config: :shared, after_change: nil, expires_in: nil) + kredis_connection_with __method__, name, key, scope: scope, default: default, config: config, after_change: after_change, expires_in: expires_in end - def kredis_decimal(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil) - kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in + def kredis_decimal(name, key: nil, scope: nil, default: nil, config: :shared, after_change: nil, expires_in: nil) + kredis_connection_with __method__, name, key, scope: scope, default: default, config: config, after_change: after_change, expires_in: expires_in end - def kredis_datetime(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil) - kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in + def kredis_datetime(name, key: nil, scope: nil, default: nil, config: :shared, after_change: nil, expires_in: nil) + kredis_connection_with __method__, name, key, scope: scope, default: default, config: config, after_change: after_change, expires_in: expires_in end - def kredis_flag(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil) - kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in + def kredis_flag(name, key: nil, scope: nil, default: nil, config: :shared, after_change: nil, expires_in: nil) + kredis_connection_with __method__, name, key, scope: scope, default: default, config: config, after_change: after_change, expires_in: expires_in define_method("#{name}?") do send(name).marked? end end - def kredis_float(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil) - kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in + def kredis_float(name, key: nil, scope: nil, default: nil, config: :shared, after_change: nil, expires_in: nil) + kredis_connection_with __method__, name, key, scope: scope, default: default, config: config, after_change: after_change, expires_in: expires_in end - 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 + def kredis_enum(name, key: nil, scope: nil, values:, default:, config: :shared, after_change: nil) + kredis_connection_with __method__, name, key, scope: scope, values: values, default: default, config: config, after_change: after_change end - def kredis_json(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil) - kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in + def kredis_json(name, key: nil, scope: nil, default: nil, config: :shared, after_change: nil, expires_in: nil) + kredis_connection_with __method__, name, key, scope: scope, default: default, config: config, after_change: after_change, expires_in: expires_in end - def kredis_list(name, key: nil, default: nil, typed: :string, config: :shared, after_change: nil) - kredis_connection_with __method__, name, key, default: default, typed: typed, config: config, after_change: after_change + def kredis_list(name, key: nil, scope: nil, default: nil, typed: :string, config: :shared, after_change: nil) + kredis_connection_with __method__, name, key, scope: scope, default: default, typed: typed, config: config, after_change: after_change end - def kredis_unique_list(name, limit: nil, key: nil, default: nil, typed: :string, config: :shared, after_change: nil) - kredis_connection_with __method__, name, key, default: default, limit: limit, typed: typed, config: config, after_change: after_change + def kredis_unique_list(name, limit: nil, key: nil, scope: nil, default: nil, typed: :string, config: :shared, after_change: nil) + kredis_connection_with __method__, name, key, scope: scope, default: default, limit: limit, typed: typed, config: config, after_change: after_change end - def kredis_set(name, key: nil, default: nil, typed: :string, config: :shared, after_change: nil) - kredis_connection_with __method__, name, key, default: default, typed: typed, config: config, after_change: after_change + def kredis_set(name, key: nil, scope: nil, default: nil, typed: :string, config: :shared, after_change: nil) + kredis_connection_with __method__, name, key, scope: scope, default: default, typed: typed, config: config, after_change: after_change end - def kredis_ordered_set(name, limit: nil, default: nil, key: nil, typed: :string, config: :shared, after_change: nil) - kredis_connection_with __method__, name, key, default: default, limit: limit, typed: typed, config: config, after_change: after_change + def kredis_ordered_set(name, limit: nil, default: nil, key: nil, scope: nil, typed: :string, config: :shared, after_change: nil) + kredis_connection_with __method__, name, key, scope: scope, default: default, limit: limit, typed: typed, config: config, after_change: after_change end - def kredis_slot(name, key: nil, config: :shared, after_change: nil) - kredis_connection_with __method__, name, key, config: config, after_change: after_change + def kredis_slot(name, key: nil, scope: nil, config: :shared, after_change: nil) + kredis_connection_with __method__, name, key, scope: scope, config: config, after_change: after_change end - 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 + def kredis_slots(name, available:, key: nil, scope: nil, config: :shared, after_change: nil) + kredis_connection_with __method__, name, key, scope: scope, available: available, config: config, after_change: after_change end - def kredis_counter(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil) - kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in + def kredis_counter(name, key: nil, scope: nil, default: nil, config: :shared, after_change: nil, expires_in: nil) + kredis_connection_with __method__, name, key, scope: scope, default: default, config: config, after_change: after_change, expires_in: expires_in end - def kredis_hash(name, key: nil, default: nil, typed: :string, config: :shared, after_change: nil) - kredis_connection_with __method__, name, key, default: default, typed: typed, config: config, after_change: after_change + def kredis_hash(name, key: nil, scope: nil, default: nil, typed: :string, config: :shared, after_change: nil) + kredis_connection_with __method__, name, key, scope: scope, default: default, typed: typed, config: config, after_change: after_change end - def kredis_boolean(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil) - kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in + def kredis_boolean(name, key: nil, scope: nil, default: nil, config: :shared, after_change: nil, expires_in: nil) + kredis_connection_with __method__, name, key, scope: scope, default: default, config: config, after_change: after_change, expires_in: expires_in end private def kredis_connection_with(method, name, key, **options) ivar_symbol = :"@#{name}_#{method}" - type = method.to_s.sub("kredis_", "") - after_change = options.delete(:after_change) define_method(name) do if instance_variable_defined?(ivar_symbol) instance_variable_get(ivar_symbol) else + type = method.to_s.delete_prefix("kredis_") + after_change = options.delete(:after_change) + scope = options.delete(:scope) options[:default] = kredis_default_evaluated(options[:default]) if options[:default] - new_type = Kredis.send(type, kredis_key_evaluated(key) || kredis_key_for_attribute(name), **options) + + new_type = Kredis.send(type, kredis_key_evaluated(scope, key, name), **options) instance_variable_set ivar_symbol, after_change ? enrich_after_change_with_record_access(new_type, after_change) : new_type end @@ -100,20 +102,30 @@ def kredis_connection_with(method, name, key, **options) end private - def kredis_key_evaluated(key) - case key - when String then key - when Proc then key.call(self) - when Symbol then send(key) + def kredis_key_evaluated(scope, key, name) + scope = case scope + when String then scope + when Proc then scope.call(self) + when Symbol then [ kredis_key_for_model(send(scope)), extract_kredis_id(send(scope)) ] + end + + custom_key = case key + when String then key + when Proc then key.call(self) + when Symbol then send(key) end + + default_key = -> { [ kredis_key_for_model, scope ? nil : extract_kredis_id, name ] } + + [ scope, (custom_key.presence || default_key.call) ].flatten.compact.join(":") end - def kredis_key_for_attribute(name) - "#{self.class.name.tableize.tr("/", ":")}:#{extract_kredis_id}:#{name}" + def kredis_key_for_model(model = self) + [ model.class.name.tableize.tr("/", ":") ] end - def extract_kredis_id - try(:id) or raise NotImplementedError, "kredis needs a unique id, either implement an id method or pass a custom key." + def extract_kredis_id(model = self) + model.try(:id) or raise NotImplementedError, "#{model.class} needs a unique id for Kredis, either implement an id method or pass a custom key" end def enrich_after_change_with_record_access(type, original_after_change) diff --git a/test/scope_test.rb b/test/scope_test.rb new file mode 100644 index 0000000..c9c800f --- /dev/null +++ b/test/scope_test.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require "test_helper" + +class Identity + def id + 1 + end +end + +class Person + include Kredis::Attributes + + kredis_list :names_with_scope, scope: :identity + kredis_list :names_with_scope_and_key, scope: :identity, key: ->(person) { "custom_key_#{person.example_method}" } + + def identity + Identity.new + end + + def example_method + "example" + end +end + +class Family + include Kredis::Attributes + + kredis_list :members + kredis_list :pets, key: "pets" + + def id + 1 + end +end + + +class ScopeTest < ActiveSupport::TestCase + setup { @person = Person.new } + + test "key is scoped" do + assert_equal @person.names_with_scope.key, "identities:1:people:names_with_scope" + end + + test "key is scoped and has custom key component" do + assert_equal @person.names_with_scope_and_key.key, "identities:1:custom_key_example" + end + + test "custom key" do + assert_equal Family.new.pets.key, "pets" + end + + test "key without scope" do + assert_equal Family.new.members.key, "families:1:members" + end +end