activekit 0.1.0 → 0.1.1
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 +4 -4
- data/lib/active_kit/engine.rb +9 -0
- data/lib/active_kit/sequence/sequenceable.rb +109 -0
- data/lib/active_kit/sequence/sequencer.rb +69 -0
- data/lib/active_kit/sequence/wordbook.rb +60 -0
- data/lib/active_kit/version.rb +1 -1
- data/lib/active_kit.rb +4 -0
- metadata +4 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d79cd1518b90bb64257a77318f59cfa3f2bfa9bb58a45ead89ddfa5bcb08b001
|
4
|
+
data.tar.gz: c8891e2351e0940b765a0a5c296403f2885fac4b64db00b822d32707f84b156c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 83975dd7d4e472532c0b1afa3afcf2aebda640093f6cedb325c9b69adc50f8216e8cc59edaea6f2d923320be8e7409e026c3d36ba48f29469acec41a97bf609b
|
7
|
+
data.tar.gz: 6bb3596f95f6893e9ce5a342b96ac22ed2d72cf36866900fa75fedb8a6a397020c0d3295160f1ddca8bee1a36921adf3e055d0e65b1b7d6d60caa6dc7975401b
|
data/lib/active_kit/engine.rb
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
module ActiveKit
|
2
2
|
class Engine < ::Rails::Engine
|
3
3
|
isolate_namespace ActiveKit
|
4
|
+
config.eager_load_namespaces << ActiveKit
|
5
|
+
|
6
|
+
initializer "active_kit.sequence" do
|
7
|
+
require "active_kit/sequence/sequenceable"
|
8
|
+
|
9
|
+
ActiveSupport.on_load(:active_record) do
|
10
|
+
include ActiveKit::Sequence::Sequenceable
|
11
|
+
end
|
12
|
+
end
|
4
13
|
end
|
5
14
|
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module ActiveKit
|
4
|
+
module Sequence
|
5
|
+
module Sequenceable
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
end
|
10
|
+
|
11
|
+
class_methods do
|
12
|
+
# Usage Options
|
13
|
+
# sequence_attribute :name
|
14
|
+
# sequence_attribute :name, :positioning_method
|
15
|
+
# sequence_attribute :name, :positioning_method, updater: {}
|
16
|
+
# sequence_attribute :name, :positioning_method, updater: { on: {} }
|
17
|
+
# sequence_attribute :name, :positioning_method, updater: { via: :assoc, on: {} }
|
18
|
+
# sequence_attribute :name, :positioning_method, updater: { via: {}, on: {} }
|
19
|
+
# Note: :on and :via in :updater can accept nested associations.
|
20
|
+
def sequence_attribute(name, positioning_method, **options)
|
21
|
+
name = name.to_sym
|
22
|
+
positioning_method = positioning_method.to_sym
|
23
|
+
options.deep_symbolize_keys!
|
24
|
+
|
25
|
+
unless self.respond_to?(:sequencer)
|
26
|
+
define_singleton_method :sequencer do
|
27
|
+
@sequencer ||= ActiveSequence::Sequencer.new(current_class: self)
|
28
|
+
end
|
29
|
+
|
30
|
+
has_many :sequence_attributes, as: :record, dependent: :destroy, class_name: "ActiveSequence::Attribute"
|
31
|
+
# scope :order_sequence, -> (options_hash) { includes(:sequence_attributes).where(sequence_attributes: { name: name.to_s }).order("sequence_attributes.value": :asc) }
|
32
|
+
end
|
33
|
+
|
34
|
+
set_active_sequence_create_callbacks(attribute_name: name)
|
35
|
+
set_active_sequence_commit_callbacks(attribute_name: name, positioning_method: positioning_method, updater: options.delete(:updater))
|
36
|
+
|
37
|
+
sequencer.add_attribute(name: name, options: options)
|
38
|
+
end
|
39
|
+
|
40
|
+
def set_active_sequence_create_callbacks(attribute_name:)
|
41
|
+
before_create do
|
42
|
+
self.sequence_attributes.find_or_initialize_by(name: attribute_name)
|
43
|
+
logger.info "ActiveSequence - Creating Sequence attribute '#{attribute}' from #{self.class.name}: Done."
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def set_active_sequence_commit_callbacks(attribute_name:, positioning_method:, updater:)
|
48
|
+
updater = updater || {}
|
49
|
+
|
50
|
+
if updater.empty?
|
51
|
+
after_commit do
|
52
|
+
position = positioning_method ? self.public_send(positioning_method) : nil
|
53
|
+
self.class.sequencer.update(record: self, attribute_name: attribute_name, position: position)
|
54
|
+
logger.info "ActiveSequence - Sequencing from #{self.class.name}: Done."
|
55
|
+
end
|
56
|
+
else
|
57
|
+
raise ":updater should be a hash while setting sequence_attribute. " unless updater.is_a?(Hash)
|
58
|
+
raise ":on in :updater should be a hash while setting sequence_attribute. " if updater.key?(:on) && !updater[:on].is_a?(Hash)
|
59
|
+
raise "Cannot use :via without :on in :updater while setting sequence_attribute. " if updater.key?(:via) && !updater.key?(:on)
|
60
|
+
|
61
|
+
updater_via = updater.delete(:via)
|
62
|
+
updater_on = updater.delete(:on) || updater
|
63
|
+
|
64
|
+
base_klass = search_base_klass(self.class.name, updater_via)
|
65
|
+
klass = reflected_klass(base_klass, updater_on.key)
|
66
|
+
klass.constantize.class_eval do
|
67
|
+
after_commit do
|
68
|
+
inverse_assoc = search_inverse_assoc(self, updater_on)
|
69
|
+
position = positioning_method ? self.public_send(positioning_method) : nil
|
70
|
+
if inverse_assoc.respond_to?(:each)
|
71
|
+
inverse_assoc.each { |instance| instance.class.sequencer.update(record: instance, attribute_name: attribute_name, position: position) }
|
72
|
+
else
|
73
|
+
inverse_assoc.class.sequencer.update(record: inverse_assoc, attribute_name: attribute_name, position: position)
|
74
|
+
end
|
75
|
+
logger.info "ActiveSequence - Sequencing from #{self.class.name}: Done."
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def search_base_klass(classname, updater_via)
|
82
|
+
if updater_via.blank?
|
83
|
+
classname
|
84
|
+
elsif updater_via.is_a? Symbol
|
85
|
+
reflected_klass(classname, updater_via)
|
86
|
+
elsif updater_via.is_a? Hash
|
87
|
+
klass = reflected_klass(classname, updater_via.key)
|
88
|
+
updater_via.value.is_a?(Hash) ? search_base_klass(klass, updater_via.value) : reflected_klass(klass, updater_via.value)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def reflected_klass(classname, key)
|
93
|
+
klass = classname.constantize.reflect_on_all_associations.map { |assoc| [assoc.name, assoc.klass.name] }.to_h[key]
|
94
|
+
raise "Could not find reflected klass for classname '#{classname}' and key '#{key}' while setting sequence_attribute" unless klass
|
95
|
+
klass
|
96
|
+
end
|
97
|
+
|
98
|
+
def search_inverse_assoc(klass_object, updater_on)
|
99
|
+
if updater_on.value.is_a?(Hash)
|
100
|
+
klass_object = klass_object.public_send(updater_on.value.key)
|
101
|
+
search_inverse_assoc(klass_object, updater_on.value)
|
102
|
+
else
|
103
|
+
klass_object.public_send(updater_on.value)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module ActiveKit
|
2
|
+
module Sequence
|
3
|
+
class Sequencer
|
4
|
+
attr_reader :defined_attributes
|
5
|
+
|
6
|
+
def initialize(current_class:)
|
7
|
+
@current_class = current_class
|
8
|
+
|
9
|
+
@defined_attributes = {}
|
10
|
+
@wordbook = Wordbook.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def update(record:, attribute_name:, position:)
|
14
|
+
attribute = Attribute.find_by(record: record, name: attribute_name)
|
15
|
+
Attribute.create!(record: record, name: attribute_name) unless attribute
|
16
|
+
|
17
|
+
if position
|
18
|
+
raise "position '#{position}' is not a valid unsigned integer value greater than 0." unless position.is_a?(Integer) && position > 0
|
19
|
+
|
20
|
+
attribute_at_position = Attribute.where(record_type: record.class.name, name: attribute_name).order(value: :asc).offset(position - 1).limit(1)
|
21
|
+
attribute
|
22
|
+
|
23
|
+
wordbook = Wordbook.new
|
24
|
+
total_position_count = Attribute.where(record_type: record.class.name, name: attribute_name).order(value: :asc).count
|
25
|
+
if attribute == attribute_at_position(position)
|
26
|
+
return
|
27
|
+
elsif position == 1
|
28
|
+
|
29
|
+
elsif position == total_position_count
|
30
|
+
wordbook.bookmark = attribute_at_position(total_position_count).value
|
31
|
+
|
32
|
+
attribute.value = wordbook.next_word if wordbook.next_word?
|
33
|
+
attribute.save!
|
34
|
+
elsif position > total_position_count
|
35
|
+
maximum_word = Attribute.where(record_type: record.class.name, name: attribute_name).maximum(:value)
|
36
|
+
|
37
|
+
wordbook.bookmark = maximum_word
|
38
|
+
attribute.value = wordbook.next_word if wordbook.next_word?
|
39
|
+
attribute.save!
|
40
|
+
elsif position < total_position_count
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def rebalance_from(position:)
|
47
|
+
ActiveRecord::Base.transaction do
|
48
|
+
wordbook = Wordbook.new
|
49
|
+
Attribute.where(record_type: record.class.name, name: attribute_name).order(value: :asc).offset(position - 1).limit(1)
|
50
|
+
Attribute.where(record_type: record.class.name, name: attribute_name).order(value: :asc).offset(position - 1).each do |attribute|
|
51
|
+
wordbook.bookmark = attribute.value
|
52
|
+
raise "Could not find next word in wordbook while rebalancing" unless wordbook.next_word?
|
53
|
+
|
54
|
+
attribute.value = wordbook.next_word
|
55
|
+
attribute.save!
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def attribute_at_position(position)
|
61
|
+
Attribute.where(record_type: record.class.name, name: attribute_name).order(value: :asc).offset(position - 1).limit(1)
|
62
|
+
end
|
63
|
+
|
64
|
+
def add_attribute(name:, options:)
|
65
|
+
@defined_attributes.store(name, options)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module ActiveKit
|
2
|
+
module Sequence
|
3
|
+
class Wordbook
|
4
|
+
attr_accessor :bookmark
|
5
|
+
|
6
|
+
def initialize(base: 36, length: 7, gap: 8, bookmark: nil)
|
7
|
+
@base = base
|
8
|
+
@length = length
|
9
|
+
@gap = gap
|
10
|
+
|
11
|
+
@first_letter = 0.to_s(@base)
|
12
|
+
@first_word = @first_letter.rjust(@length, @first_letter)
|
13
|
+
|
14
|
+
@last_letter = (@base - 1).to_s(@base)
|
15
|
+
@last_word = @last_letter.rjust(@length, @last_letter)
|
16
|
+
|
17
|
+
@bookmark = bookmark || @first_word
|
18
|
+
end
|
19
|
+
|
20
|
+
def previous_word(count: 1)
|
21
|
+
new_word(direction: :previous, count: count)
|
22
|
+
end
|
23
|
+
|
24
|
+
def previous_word?(count: 1)
|
25
|
+
previous_word(count: count).present?
|
26
|
+
end
|
27
|
+
|
28
|
+
def next_word(count: 1)
|
29
|
+
new_word(direction: :next, count: count)
|
30
|
+
end
|
31
|
+
|
32
|
+
def next_word?(count: 1)
|
33
|
+
next_word(count: count).present?
|
34
|
+
end
|
35
|
+
|
36
|
+
def between_word(word_one:, word_two:)
|
37
|
+
raise "'#{word_one}' is not in range." if (word_one.length > @length) || (word_one < @first_word || word_one > @last_word)
|
38
|
+
raise "'#{word_two}' is not in range." if (word_two.length > @length) || (word_two < @first_word || word_two > @last_word)
|
39
|
+
|
40
|
+
diff = word_one > word_two ? word_one.to_i(@base) - word_two.to_i(@base) : word_two.to_i(@base) - word_one.to_i(@base)
|
41
|
+
between_word = (diff / 2).to_s(@base)
|
42
|
+
between_word.rjust(@length, @first_letter)
|
43
|
+
end
|
44
|
+
|
45
|
+
def between_word?(word_one:, word_two:)
|
46
|
+
between_word(word_one: word_one, word_two: word_two).present?
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def new_word(direction:, count:)
|
52
|
+
word = @bookmark.to_i(@base)
|
53
|
+
word = direction == :next ? word + (count * @gap) : word - (count * @gap)
|
54
|
+
word = word.to_s(@base).rjust(@length, @first_letter)
|
55
|
+
# TODO: raise exception if word is out of bound before first word or after last word.
|
56
|
+
word
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/active_kit/version.rb
CHANGED
data/lib/active_kit.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activekit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- plainsource
|
@@ -51,6 +51,9 @@ files:
|
|
51
51
|
- config/routes.rb
|
52
52
|
- lib/active_kit.rb
|
53
53
|
- lib/active_kit/engine.rb
|
54
|
+
- lib/active_kit/sequence/sequenceable.rb
|
55
|
+
- lib/active_kit/sequence/sequencer.rb
|
56
|
+
- lib/active_kit/sequence/wordbook.rb
|
54
57
|
- lib/active_kit/version.rb
|
55
58
|
- lib/activekit.rb
|
56
59
|
- lib/tasks/active_kit_tasks.rake
|