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

Scoped attribute keys with a generated prefix #125

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
108 changes: 60 additions & 48 deletions lib/kredis/attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
64 changes: 64 additions & 0 deletions test/scope_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# frozen_string_literal: true

require "test_helper"

class ScopeTest < ActiveSupport::TestCase
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"
kredis_list :members_with_nil_scope, scope: ->(person) { nil }

def id
1
end
end


setup do
@person = Person.new
@family = Family.new
end

test "key is scoped" do
assert_equal @person.names_with_scope.key, "scope_test:identities:1:scope_test:people:names_with_scope"
end

test "key is scoped and has custom key component" do
assert_equal @person.names_with_scope_and_key.key, "scope_test:identities:1:custom_key_example"
end

test "scope is nil and key is generated normally" do
assert_equal @family.members_with_nil_scope.key, "scope_test:families:1:members_with_nil_scope"
end

test "custom key" do
assert_equal @family.pets.key, "pets"
end

test "key without scope" do
assert_equal @family.members.key, "scope_test:families:1:members"
end
end