activekit 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cfed47304194f32cec27e3c9fa345f1da862d3778591e9bd2abca1dbb72be09f
4
- data.tar.gz: d49567af80349c8c0433e102ccc72fd2399667dcac11511e6b1b2c7a7436a41f
3
+ metadata.gz: f22601f05c64e21478512fd85ac1ebf74afc91c517349793fc79bfad4f343952
4
+ data.tar.gz: 3ed1dcb02993f21dd6cb582b2574652eb34a5107e4d70c456ba047391294a908
5
5
  SHA512:
6
- metadata.gz: a98525862f0a8aecfd0b9c02d51903e45a8977634fb8b3a489982b5465d8dc00ad6581c36f6b740fac413c7708be4ac38726a8fd5b3f49ee4ac1b1b8148103e5
7
- data.tar.gz: 349abf115fed3456d6e996ac52480941d9774ec06cdeef96c821d6d17298b8b81d94176aa08aafbe92bfab3c713d7a6563c35c1258aa958828366824367cc488
6
+ metadata.gz: 78245200325e89c2a78a83123c2874bb9163d2202fdf9719b62f60d9986274a6ddb9e1700801cb2d7be896aaff90d782f79e43c9e6681c8720192e14e4738acd
7
+ data.tar.gz: d770761da113c6f859e04250a980f3c4416ff142702ab2f97b88ca32bc063cb4f32e9967e0fa2c2b4deac10e5d4d63c392e00c5462f31a586aefa355b3f87918
@@ -0,0 +1,7 @@
1
+ module ActiveKit
2
+ class Attribute < ApplicationRecord
3
+ belongs_to :record, polymorphic: true
4
+
5
+ validates :value, presence: true, length: { maximum: 1073741823, allow_blank: true }
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ class CreateActiveKitAttributes < ActiveRecord::Migration[6.1]
2
+ def change
3
+ create_table :active_kit_attributes do |t|
4
+ t.references :record, polymorphic: true, index: true
5
+ t.text :value, size: :long
6
+ t.timestamps
7
+ end
8
+ end
9
+ end
@@ -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
@@ -1,3 +1,3 @@
1
1
  module ActiveKit
2
- VERSION = '0.1.0'
2
+ VERSION = '0.1.2'
3
3
  end
data/lib/active_kit.rb CHANGED
@@ -2,4 +2,8 @@ require "active_kit/version"
2
2
  require "active_kit/engine"
3
3
 
4
4
  module ActiveKit
5
+ extend ActiveSupport::Autoload
6
+
7
+ autoload :Sequencer, "active_kit/sequence/sequencer"
8
+ autoload :Wordbook, "active_kit/sequence/wordbook"
5
9
  end
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.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - plainsource
@@ -47,10 +47,15 @@ files:
47
47
  - app/jobs/active_kit/application_job.rb
48
48
  - app/mailers/active_kit/application_mailer.rb
49
49
  - app/models/active_kit/application_record.rb
50
+ - app/models/active_kit/attribute.rb
50
51
  - app/views/layouts/active_kit/application.html.erb
51
52
  - config/routes.rb
53
+ - db/migrate/20231016050208_create_active_kit_attributes.rb
52
54
  - lib/active_kit.rb
53
55
  - lib/active_kit/engine.rb
56
+ - lib/active_kit/sequence/sequenceable.rb
57
+ - lib/active_kit/sequence/sequencer.rb
58
+ - lib/active_kit/sequence/wordbook.rb
54
59
  - lib/active_kit/version.rb
55
60
  - lib/activekit.rb
56
61
  - lib/tasks/active_kit_tasks.rake