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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cfed47304194f32cec27e3c9fa345f1da862d3778591e9bd2abca1dbb72be09f
4
- data.tar.gz: d49567af80349c8c0433e102ccc72fd2399667dcac11511e6b1b2c7a7436a41f
3
+ metadata.gz: d79cd1518b90bb64257a77318f59cfa3f2bfa9bb58a45ead89ddfa5bcb08b001
4
+ data.tar.gz: c8891e2351e0940b765a0a5c296403f2885fac4b64db00b822d32707f84b156c
5
5
  SHA512:
6
- metadata.gz: a98525862f0a8aecfd0b9c02d51903e45a8977634fb8b3a489982b5465d8dc00ad6581c36f6b740fac413c7708be4ac38726a8fd5b3f49ee4ac1b1b8148103e5
7
- data.tar.gz: 349abf115fed3456d6e996ac52480941d9774ec06cdeef96c821d6d17298b8b81d94176aa08aafbe92bfab3c713d7a6563c35c1258aa958828366824367cc488
6
+ metadata.gz: 83975dd7d4e472532c0b1afa3afcf2aebda640093f6cedb325c9b69adc50f8216e8cc59edaea6f2d923320be8e7409e026c3d36ba48f29469acec41a97bf609b
7
+ data.tar.gz: 6bb3596f95f6893e9ce5a342b96ac22ed2d72cf36866900fa75fedb8a6a397020c0d3295160f1ddca8bee1a36921adf3e055d0e65b1b7d6d60caa6dc7975401b
@@ -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.1'
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.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