activekit 0.1.0 → 0.1.1

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: 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