activekit 0.2.3 → 0.3.0
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/MIT-LICENSE +1 -1
- data/README.md +40 -1
- data/lib/active_kit/engine.rb +5 -5
- data/lib/active_kit/position/harmonize.rb +71 -0
- data/lib/active_kit/{base → position}/middleware.rb +5 -4
- data/lib/active_kit/position/positionable.rb +77 -0
- data/lib/active_kit/position/positioner.rb +87 -0
- data/lib/active_kit/position/positioning.rb +86 -0
- data/lib/active_kit/position.rb +9 -0
- data/lib/active_kit/version.rb +1 -1
- data/lib/active_kit.rb +1 -2
- metadata +8 -14
- data/app/models/active_kit/attribute.rb +0 -17
- data/db/migrate/20231016050208_create_active_kit_attributes.rb +0 -9
- data/lib/active_kit/base/activekitable.rb +0 -17
- data/lib/active_kit/base/activekiter.rb +0 -13
- data/lib/active_kit/base/ensure.rb +0 -25
- data/lib/active_kit/base/relation.rb +0 -9
- data/lib/active_kit/base.rb +0 -9
- data/lib/active_kit/sequence/sequence.rb +0 -38
- data/lib/active_kit/sequence/sequenceable.rb +0 -98
- data/lib/active_kit/sequence/wordbook.rb +0 -60
- data/lib/active_kit/sequence.rb +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c2d19ba43a09297c57c1b373864a53bf9b68787fe3a5b46a5167ae178db0800a
|
4
|
+
data.tar.gz: fee7a6d8f645787a847fd33a3500d0aade7162a8ff855c45349e45f77d4fe437
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b4e68fd3ee58d3c8d2d7a40995a12fcce17f8264548eb7a178422a009706741833c727f723cc8888d66409affbe77770c77eced0d75d1c1249ec9c335a54512
|
7
|
+
data.tar.gz: 0b551a885c6e90178aa7711d8c6356b448a57a28d319ce3daf230e93b2c5d92bd3a2a10f70489ee0cd5f9f77f2490cd6962c33426a089d25dc209d4d9f6d73a5
|
data/MIT-LICENSE
CHANGED
data/README.md
CHANGED
@@ -2,7 +2,46 @@
|
|
2
2
|
Add the essential kit for rails ActiveRecord models and be happy.
|
3
3
|
|
4
4
|
## Usage
|
5
|
-
|
5
|
+
|
6
|
+
### Position Attribute
|
7
|
+
|
8
|
+
Add positioning to your ActiveRecord models.
|
9
|
+
Position Attribute provides full positioning functionality using lexicographic ordering for your model database records.
|
10
|
+
|
11
|
+
You can also add multiple position attributes in one model to store different arrangements.
|
12
|
+
|
13
|
+
Just create a database column in your model with type :string with index.
|
14
|
+
```ruby
|
15
|
+
add_column :products, :arrangement, :string, index: true, before: :created_at
|
16
|
+
```
|
17
|
+
|
18
|
+
Then define the column name in your model like below.
|
19
|
+
```ruby
|
20
|
+
class Product < ApplicationRecord
|
21
|
+
position_attribute :arrangement
|
22
|
+
end
|
23
|
+
```
|
24
|
+
|
25
|
+
Creating the record will automatically set it to the last position:
|
26
|
+
```ruby
|
27
|
+
product = Product.create(name: "Nice Product")
|
28
|
+
```
|
29
|
+
|
30
|
+
The following attribute methods will be added to your model object to use:
|
31
|
+
```ruby
|
32
|
+
product.arrangement_position = 1 # Set the position
|
33
|
+
product.arrangement_position # Access the manually set position
|
34
|
+
product.arrangement_position_in_database # Check the position as per database
|
35
|
+
product.arrangement_position_options # Can be used in dropdown
|
36
|
+
product.arrangement_position_maximum # Check current maximum position
|
37
|
+
```
|
38
|
+
|
39
|
+
The following class methods will be added to your model class to use:
|
40
|
+
```ruby
|
41
|
+
# If a database table already has existing rows, run this to set initial values.
|
42
|
+
# You can also run this to manually harmonize a position attribute column.
|
43
|
+
Product.harmonize_arrangement!
|
44
|
+
```
|
6
45
|
|
7
46
|
## Installation
|
8
47
|
Add this line to your application's Gemfile:
|
data/lib/active_kit/engine.rb
CHANGED
@@ -4,16 +4,16 @@ module ActiveKit
|
|
4
4
|
config.eager_load_namespaces << ActiveKit
|
5
5
|
|
6
6
|
initializer "active_kit.add_middleware" do |app|
|
7
|
-
require "active_kit/
|
7
|
+
require "active_kit/position/middleware"
|
8
8
|
|
9
|
-
app.middleware.use ActiveKit::
|
9
|
+
app.middleware.use ActiveKit::Position::Middleware
|
10
10
|
end
|
11
11
|
|
12
|
-
initializer "active_kit.
|
13
|
-
require "active_kit/
|
12
|
+
initializer "active_kit.position" do
|
13
|
+
require "active_kit/position/positionable"
|
14
14
|
|
15
15
|
ActiveSupport.on_load(:active_record) do
|
16
|
-
include ActiveKit::
|
16
|
+
include ActiveKit::Position::Positionable
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module ActiveKit
|
2
|
+
module Position
|
3
|
+
class Harmonize
|
4
|
+
def initialize(current_class:, name:, scope:)
|
5
|
+
@current_class = current_class
|
6
|
+
@name = name
|
7
|
+
|
8
|
+
@scoped_class = @current_class.where(scope)
|
9
|
+
@positioning = Positioning.new
|
10
|
+
@batch_size = 1000
|
11
|
+
end
|
12
|
+
|
13
|
+
def run!
|
14
|
+
@current_class.transaction do
|
15
|
+
chair_at_params, scoped_class_with_order, chair_method, offset_operator = control
|
16
|
+
currvalue = @positioning.chair_at(**chair_at_params, increase_spot_length_by: 1).first
|
17
|
+
|
18
|
+
first_run = true
|
19
|
+
where_offset = nil
|
20
|
+
loop do
|
21
|
+
records = scoped_class_with_order.where(where_offset).limit(@batch_size)
|
22
|
+
break if records.empty?
|
23
|
+
|
24
|
+
records.lock.each do |record|
|
25
|
+
value, reharmonize = first_run ? [currvalue, false] : @positioning.public_send(chair_method, currvalue: currvalue)
|
26
|
+
raise message_for_reharmonize if reharmonize
|
27
|
+
|
28
|
+
record.send("#{@name}=", value)
|
29
|
+
record.save!
|
30
|
+
|
31
|
+
currvalue = record.public_send("#{@name}")
|
32
|
+
first_run = false
|
33
|
+
end
|
34
|
+
|
35
|
+
where_offset = ["#{@name} #{offset_operator} ?", currvalue]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
Rails.logger.info "ActiveKit::Position | Harmonize for :#{@name}: completed."
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def control
|
45
|
+
records = @scoped_class.where.not("#{@name}": nil).order("#{@name}": :asc, id: :asc).select(@name.to_sym)
|
46
|
+
headtier = records.first&.try(@name.to_sym)&.split("|")&.first&.last&.to_i # returns a tire integer
|
47
|
+
foottier = records.last&.try(@name.to_sym)&.split("|")&.first&.last&.to_i # returns a tire integer
|
48
|
+
|
49
|
+
if headtier == foottier
|
50
|
+
nexttier, ordering = (headtier == 0) ? [1, :foot_to_head] : [0, :head_to_foot]
|
51
|
+
else
|
52
|
+
maxitier = (headtier.nil? || foottier.nil?) ? (headtier.nil? ? foottier : headtier) : (headtier > foottier ? headtier : foottier)
|
53
|
+
nexttier, ordering = (maxitier + 1), :foot_to_head
|
54
|
+
end
|
55
|
+
scoped_order, chair_method, offset_operator = (ordering == :head_to_foot) ? [:asc, :chair_below, ">"] : [:desc, :chair_above, "<"]
|
56
|
+
|
57
|
+
scoped_class_with_order = @scoped_class.order("#{@name}": scoped_order, id: scoped_order)
|
58
|
+
scoped_class_with_order_count = scoped_class_with_order.count
|
59
|
+
initial_position = (ordering == :head_to_foot) ? 1 : scoped_class_with_order_count
|
60
|
+
|
61
|
+
chair_at_params = { position: initial_position, tier_no: nexttier, total_count: scoped_class_with_order_count }
|
62
|
+
|
63
|
+
[chair_at_params, scoped_class_with_order, chair_method, offset_operator]
|
64
|
+
end
|
65
|
+
|
66
|
+
def message_for_reharmonize
|
67
|
+
"Harmonize cannot ask to harmonize again. Please check values of attribute '#{@name}' and try again."
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -1,22 +1,23 @@
|
|
1
1
|
module ActiveKit
|
2
|
-
module
|
2
|
+
module Position
|
3
3
|
class Middleware
|
4
4
|
def initialize(app)
|
5
5
|
@app = app
|
6
6
|
end
|
7
7
|
|
8
|
-
# Middleware that determines which ActiveKit middlewares to run.
|
9
8
|
def call(env)
|
10
9
|
request = ActionDispatch::Request.new(env)
|
11
10
|
|
12
|
-
|
11
|
+
middleware_run(request) do
|
13
12
|
@app.call(env)
|
14
13
|
end
|
15
14
|
end
|
16
15
|
|
17
16
|
private
|
18
17
|
|
19
|
-
def
|
18
|
+
def middleware_run(request, &blk)
|
19
|
+
# Position middleware code
|
20
|
+
|
20
21
|
yield
|
21
22
|
end
|
22
23
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module ActiveKit
|
4
|
+
module Position
|
5
|
+
module Positionable
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
end
|
10
|
+
|
11
|
+
class_methods do
|
12
|
+
def position_attribute(name, **options)
|
13
|
+
scope = options[:scope] || {}
|
14
|
+
|
15
|
+
attribute "#{name}_position", :integer
|
16
|
+
|
17
|
+
validates "#{name}", presence: true, uniqueness: { conditions: -> { where(scope) }, case_sensitive: false, allow_blank: true }, length: { maximum: 255, allow_blank: true }
|
18
|
+
validates "#{name}_position", numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: lambda { |record| record.public_send("#{name}_position_maximum") + 1 }, allow_blank: true }
|
19
|
+
|
20
|
+
before_validation "#{name}_reposition".to_sym
|
21
|
+
after_commit "#{name}_reharmonize".to_sym
|
22
|
+
|
23
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
24
|
+
def #{name}_position_in_database
|
25
|
+
self.#{name}_positioner.position_in_database
|
26
|
+
end
|
27
|
+
|
28
|
+
def #{name}_position_options
|
29
|
+
self.#{name}_positioner.position_options
|
30
|
+
end
|
31
|
+
|
32
|
+
def #{name}_position_maximum
|
33
|
+
self.#{name}_positioner.position_maximum
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.harmonize_#{name}!
|
37
|
+
ActiveKit::Position::Harmonize.new(current_class: self, name: '#{name}', scope: #{scope}).run!
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def #{name}=(value)
|
43
|
+
super(value)
|
44
|
+
end
|
45
|
+
|
46
|
+
def #{name}_positioner
|
47
|
+
@#{name}_positioner ||= ActiveKit::Position::Positioner.new(record: self, name: '#{name}', scope: #{scope})
|
48
|
+
end
|
49
|
+
|
50
|
+
def #{name}_reposition
|
51
|
+
return unless (self.#{name}.blank? && self.#{name}_position.blank?) || self.#{name}_position.present?
|
52
|
+
|
53
|
+
position_maximum_cached = self.#{name}_positioner.position_maximum
|
54
|
+
|
55
|
+
if self.#{name}.blank? && self.#{name}_position.blank?
|
56
|
+
self.#{name}_position = position_maximum_cached + 1
|
57
|
+
end
|
58
|
+
|
59
|
+
if self.#{name}_position.present? && self.#{name}_position >= 1 && self.#{name}_position <= (position_maximum_cached + 1)
|
60
|
+
if self.#{name}_position != #{name}_position_in_database
|
61
|
+
self.#{name} = self.#{name}_positioner.spot_for(position: self.#{name}_position, position_maximum_cached: position_maximum_cached)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def #{name}_reharmonize
|
67
|
+
return unless self.#{name}_positioner.reharmonize?
|
68
|
+
|
69
|
+
self.class.harmonize_#{name}!
|
70
|
+
self.#{name}_positioner.reharmonized!
|
71
|
+
end
|
72
|
+
CODE
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module ActiveKit
|
2
|
+
module Position
|
3
|
+
class Positioner
|
4
|
+
def initialize(record:, name:, scope:)
|
5
|
+
@record = record
|
6
|
+
@name = name
|
7
|
+
|
8
|
+
@scoped_class = @record.class.where(scope).order("#{@name}": :asc)
|
9
|
+
@reharmonize = false
|
10
|
+
@positioning = Positioning.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def position_in_database
|
14
|
+
@scoped_class.where("#{@name}": ..@record.public_send("#{@name}_in_database")).count if @record.public_send("#{@name}_in_database")
|
15
|
+
end
|
16
|
+
|
17
|
+
def position_options
|
18
|
+
(1..position_maximum).map { |position| [position, "Position #{position}"] }
|
19
|
+
end
|
20
|
+
|
21
|
+
def position_maximum
|
22
|
+
value = self.maxivalue
|
23
|
+
value ? @scoped_class.where("#{@name}": ..value).count : 0
|
24
|
+
end
|
25
|
+
|
26
|
+
def spot_for(position:, position_maximum_cached:)
|
27
|
+
raise "position or position_maximum_cached cannot be empty for spot_for in activekit." unless position && position_maximum_cached
|
28
|
+
|
29
|
+
edge_position = position_maximum_cached + 1
|
30
|
+
if position == edge_position && position == 1
|
31
|
+
value, @reharmonize = @positioning.chair_first
|
32
|
+
elsif position == edge_position
|
33
|
+
value, @reharmonize = @positioning.chair_below(currvalue: self.maxivalue)
|
34
|
+
elsif position_in_database.nil?
|
35
|
+
value, @reharmonize = @positioning.stool_above(currvalue: currvalue(position, position_maximum_cached),
|
36
|
+
prevvalue: prevvalue(position, position_maximum_cached))
|
37
|
+
elsif position > position_in_database
|
38
|
+
value, @reharmonize = @positioning.stool_below(currvalue: currvalue(position, position_maximum_cached),
|
39
|
+
nextvalue: nextvalue(position, position_maximum_cached))
|
40
|
+
else
|
41
|
+
value, @reharmonize = @positioning.stool_above(currvalue: currvalue(position, position_maximum_cached),
|
42
|
+
prevvalue: prevvalue(position, position_maximum_cached))
|
43
|
+
end
|
44
|
+
|
45
|
+
value
|
46
|
+
end
|
47
|
+
|
48
|
+
def reharmonize?
|
49
|
+
@reharmonize
|
50
|
+
end
|
51
|
+
|
52
|
+
def reharmonized!
|
53
|
+
@reharmonize = false
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def maxivalue
|
59
|
+
@scoped_class.last&.public_send(@name)
|
60
|
+
end
|
61
|
+
|
62
|
+
def prevvalue(position, position_maximum_cached)
|
63
|
+
prevvalue?(position, position_maximum_cached) ? @scoped_class.offset(position - 2).first.public_send(@name) : nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def currvalue(position, position_maximum_cached)
|
67
|
+
currvalue?(position, position_maximum_cached) ? @scoped_class.offset(position - 1).first.public_send(@name) : nil
|
68
|
+
end
|
69
|
+
|
70
|
+
def nextvalue(position, position_maximum_cached)
|
71
|
+
nextvalue?(position, position_maximum_cached) ? @scoped_class.offset(position - 0).first.public_send(@name) : nil
|
72
|
+
end
|
73
|
+
|
74
|
+
def prevvalue?(position, position_maximum_cached)
|
75
|
+
position > 1
|
76
|
+
end
|
77
|
+
|
78
|
+
def currvalue?(position, position_maximum_cached)
|
79
|
+
position >= 1 && position <= position_maximum_cached
|
80
|
+
end
|
81
|
+
|
82
|
+
def nextvalue?(position, position_maximum_cached)
|
83
|
+
position < position_maximum_cached
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module ActiveKit
|
2
|
+
module Position
|
3
|
+
class Positioning
|
4
|
+
BASE = 36
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@initial_tier = "t0"
|
8
|
+
@initial_spot = "00"
|
9
|
+
@initial_slot = "hz"
|
10
|
+
end
|
11
|
+
|
12
|
+
def chair_first
|
13
|
+
chair(@initial_tier, @initial_spot, @initial_slot)
|
14
|
+
end
|
15
|
+
|
16
|
+
def chair_at(position:, tier_no:, total_count:, increase_spot_length_by:)
|
17
|
+
nicetire = "t#{tier_no}"
|
18
|
+
nicespot = (position - 1).to_s(BASE)
|
19
|
+
nicespot = nicespot.rjust((total_count - 1).to_s(BASE).length + increase_spot_length_by, "0")
|
20
|
+
chair(nicetire, nicespot, @initial_slot)
|
21
|
+
end
|
22
|
+
|
23
|
+
def chair_above(currvalue:)
|
24
|
+
currtier, currspot = currvalue.split("|").take(2)
|
25
|
+
nicespot = (currspot.to_i(BASE) - 1).to_s(BASE).rjust(currspot.length, "0")
|
26
|
+
nicespot = firstspot(currspot) if nicespot.to_i(BASE) == 0
|
27
|
+
chair(currtier, nicespot, @initial_slot)
|
28
|
+
end
|
29
|
+
|
30
|
+
def chair_below(currvalue:)
|
31
|
+
currtier, currspot = currvalue.split("|").take(2)
|
32
|
+
nicespot = (currspot.to_i(BASE) + 1).to_s(BASE).rjust(currspot.length, "0")
|
33
|
+
nicespot = finalspot(currspot) if nicespot.to_i(BASE) > finalspot(currspot).to_i(BASE)
|
34
|
+
chair(currtier, nicespot, @initial_slot)
|
35
|
+
end
|
36
|
+
|
37
|
+
def stool_above(currvalue:, prevvalue:)
|
38
|
+
currtier, currspot, currslot = currvalue.split("|")
|
39
|
+
prevtier, prevspot, prevslot = prevvalue.split("|") if prevvalue
|
40
|
+
stool(currtier, currspot, firstslot(currtier, prevtier, currspot, prevspot, prevslot), currslot)
|
41
|
+
end
|
42
|
+
|
43
|
+
def stool_below(currvalue:, nextvalue:)
|
44
|
+
currtier, currspot, currslot = currvalue.split("|")
|
45
|
+
nexttier, nextspot, nextslot = nextvalue.split("|") if nextvalue
|
46
|
+
stool(currtier, currspot, currslot, finalslot(currtier, nexttier, currspot, nextspot, nextslot))
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def firstspot(currspot)
|
52
|
+
"0".rjust(currspot.length, "0")
|
53
|
+
end
|
54
|
+
|
55
|
+
def finalspot(currspot)
|
56
|
+
"z".rjust(currspot.length, "z")
|
57
|
+
end
|
58
|
+
|
59
|
+
def firstslot(currtier, prevtier, currspot, prevspot, prevslot)
|
60
|
+
currtier == prevtier && currspot == prevspot ? prevslot : "00"
|
61
|
+
end
|
62
|
+
|
63
|
+
def finalslot(currtier, nexttier, currspot, nextspot, nextslot)
|
64
|
+
currtier == nexttier && currspot == nextspot ? nextslot : "zz"
|
65
|
+
end
|
66
|
+
|
67
|
+
def chair(tier, spot, slot)
|
68
|
+
["#{tier}|#{spot}|#{slot}", chairs_almost_over?(spot)]
|
69
|
+
end
|
70
|
+
|
71
|
+
def stool(tier, spot, slot_small, slot_big)
|
72
|
+
avg_slot = ((slot_big.to_i(BASE) + slot_small.to_i(BASE)) / 2).to_s(BASE).rjust(slot_big.length, "0")
|
73
|
+
["#{tier}|#{spot}|#{avg_slot}", stools_almost_over?(slot_small, slot_big)]
|
74
|
+
end
|
75
|
+
|
76
|
+
def chairs_almost_over?(spot)
|
77
|
+
finalspot(spot).to_i(BASE) - spot.to_i(BASE) <= 10
|
78
|
+
end
|
79
|
+
|
80
|
+
# Careful, when 'true' stools are much less than 10 because of division by 2 to get the next stool.
|
81
|
+
def stools_almost_over?(slot_small, slot_big)
|
82
|
+
(slot_big.to_i(BASE) - slot_small.to_i(BASE)) <= 10
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/active_kit/version.rb
CHANGED
data/lib/active_kit.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activekit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- plainsource
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-12-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -47,22 +47,16 @@ 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
|
51
50
|
- app/views/layouts/active_kit/application.html.erb
|
52
51
|
- config/routes.rb
|
53
|
-
- db/migrate/20231016050208_create_active_kit_attributes.rb
|
54
52
|
- lib/active_kit.rb
|
55
|
-
- lib/active_kit/base.rb
|
56
|
-
- lib/active_kit/base/activekitable.rb
|
57
|
-
- lib/active_kit/base/activekiter.rb
|
58
|
-
- lib/active_kit/base/ensure.rb
|
59
|
-
- lib/active_kit/base/middleware.rb
|
60
|
-
- lib/active_kit/base/relation.rb
|
61
53
|
- lib/active_kit/engine.rb
|
62
|
-
- lib/active_kit/
|
63
|
-
- lib/active_kit/
|
64
|
-
- lib/active_kit/
|
65
|
-
- lib/active_kit/
|
54
|
+
- lib/active_kit/position.rb
|
55
|
+
- lib/active_kit/position/harmonize.rb
|
56
|
+
- lib/active_kit/position/middleware.rb
|
57
|
+
- lib/active_kit/position/positionable.rb
|
58
|
+
- lib/active_kit/position/positioner.rb
|
59
|
+
- lib/active_kit/position/positioning.rb
|
66
60
|
- lib/active_kit/version.rb
|
67
61
|
- lib/activekit.rb
|
68
62
|
- lib/tasks/active_kit_tasks.rake
|
@@ -1,17 +0,0 @@
|
|
1
|
-
module ActiveKit
|
2
|
-
class Attribute < ApplicationRecord
|
3
|
-
belongs_to :record, polymorphic: true
|
4
|
-
|
5
|
-
store :value, accessors: [ :sequence ], coder: JSON
|
6
|
-
|
7
|
-
validates :value, presence: true, length: { maximum: 1073741823, allow_blank: true }
|
8
|
-
|
9
|
-
before_validation :set_defaults
|
10
|
-
|
11
|
-
private
|
12
|
-
|
13
|
-
def set_defaults
|
14
|
-
self.sequence = { attributes: {} } if self.sequence.blank?
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
@@ -1,17 +0,0 @@
|
|
1
|
-
require 'active_support/concern'
|
2
|
-
require "active_kit/sequence/sequenceable"
|
3
|
-
|
4
|
-
module ActiveKit
|
5
|
-
module Base
|
6
|
-
module Activekitable
|
7
|
-
extend ActiveSupport::Concern
|
8
|
-
include ActiveKit::Sequence::Sequenceable
|
9
|
-
|
10
|
-
included do
|
11
|
-
end
|
12
|
-
|
13
|
-
class_methods do
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
module ActiveKit
|
2
|
-
module Base
|
3
|
-
class Ensure
|
4
|
-
def self.setup_for!(current_class:)
|
5
|
-
current_class.class_eval do
|
6
|
-
unless self.reflect_on_association :activekit_association
|
7
|
-
has_one :activekit_association, as: :record, dependent: :destroy, class_name: "ActiveKit::Attribute"
|
8
|
-
|
9
|
-
def activekit
|
10
|
-
@activekit ||= Relation.new(current_object: self)
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.activekiter
|
14
|
-
@activekiter ||= Activekiter.new(current_class: self)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.has_one_association_for!(record:)
|
21
|
-
record.create_activekit_association unless record.activekit_association
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
data/lib/active_kit/base.rb
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
module ActiveKit
|
2
|
-
module Sequence
|
3
|
-
class Sequence
|
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
|
-
ActiveKit::Base::Ensure.has_one_association_for!(record: record)
|
15
|
-
|
16
|
-
if position
|
17
|
-
raise "position '#{position}' is not a valid unsigned integer value greater than 0." unless position.is_a?(Integer) && position > 0
|
18
|
-
|
19
|
-
wordbook = Wordbook.new
|
20
|
-
word_for_position = wordbook.next_word(count: position)
|
21
|
-
# TODO: committer record for the attribute with given word_for_position should be found and resaved to recalculate its position.
|
22
|
-
# json_where = 'value->"$.sequence.attributes.' + attribute_name.to_s + '" = "' + word_for_position + '"'
|
23
|
-
# record_at_position = ActiveKit::Attribute.where(record_type: record.class.name).where(json_where).first&.record
|
24
|
-
record.activekit_association.sequence[:attributes][attribute_name.to_sym] = word_for_position
|
25
|
-
record.activekit_association.save!
|
26
|
-
# record_at_position.save! if record_at_position
|
27
|
-
else
|
28
|
-
record.activekit_association.sequence[:attributes][attribute_name.to_sym] = nil
|
29
|
-
record.activekit_association.save!
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def add_attribute(name:, options:)
|
34
|
-
@defined_attributes.store(name, options)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
@@ -1,98 +0,0 @@
|
|
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 = nil, **options)
|
21
|
-
ActiveKit::Base::Ensure.setup_for!(current_class: self)
|
22
|
-
|
23
|
-
name = name.to_sym
|
24
|
-
options.store(:positioning_method, positioning_method&.to_sym)
|
25
|
-
options.deep_symbolize_keys!
|
26
|
-
|
27
|
-
set_active_sequence_callbacks(attribute_name: name, options: options)
|
28
|
-
activekiter.sequence.add_attribute(name: name, options: options)
|
29
|
-
end
|
30
|
-
|
31
|
-
def set_active_sequence_callbacks(attribute_name:, options:)
|
32
|
-
positioning_method = options.dig(:positioning_method)
|
33
|
-
updater = options.dig(:updater) || {}
|
34
|
-
|
35
|
-
if updater.empty?
|
36
|
-
after_save do
|
37
|
-
position = positioning_method ? self.public_send(positioning_method) : nil
|
38
|
-
self.class.activekiter.sequence.update(record: self, attribute_name: attribute_name, position: position)
|
39
|
-
logger.info "ActiveSequence - Sequencing from #{self.class.name}: Done."
|
40
|
-
end
|
41
|
-
else
|
42
|
-
raise ":updater should be a hash while setting sequence_attribute. " unless updater.is_a?(Hash)
|
43
|
-
raise ":on in :updater should be a hash while setting sequence_attribute. " if updater.key?(:on) && !updater[:on].is_a?(Hash)
|
44
|
-
raise "Cannot use :via without :on in :updater while setting sequence_attribute. " if updater.key?(:via) && !updater.key?(:on)
|
45
|
-
|
46
|
-
updater_via = updater.delete(:via)
|
47
|
-
updater_on = updater.delete(:on) || updater
|
48
|
-
|
49
|
-
base_klass = search_base_klass(self.name, updater_via)
|
50
|
-
klass = reflected_klass(base_klass, updater_on.keys.first)
|
51
|
-
klass.constantize.class_eval do
|
52
|
-
after_save :activekit_sequence_sequenceable_callback
|
53
|
-
after_destroy :activekit_sequence_sequenceable_callback
|
54
|
-
|
55
|
-
define_method :activekit_sequence_sequenceable_callback do
|
56
|
-
inverse_assoc = self.class.search_inverse_assoc(self, updater_on)
|
57
|
-
position = positioning_method ? self.public_send(positioning_method) : nil
|
58
|
-
if inverse_assoc.respond_to?(:each)
|
59
|
-
inverse_assoc.each { |instance| instance.class.activekiter.sequence.update(record: instance, attribute_name: attribute_name, position: position) }
|
60
|
-
else
|
61
|
-
inverse_assoc.class.activekiter.sequence.update(record: inverse_assoc, attribute_name: attribute_name, position: position)
|
62
|
-
end
|
63
|
-
logger.info "ActiveSequence - Sequencing from #{self.class.name}: Done."
|
64
|
-
end
|
65
|
-
private :activekit_sequence_sequenceable_callback
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def search_base_klass(classname, updater_via)
|
71
|
-
if updater_via.blank?
|
72
|
-
classname
|
73
|
-
elsif updater_via.is_a? Symbol
|
74
|
-
reflected_klass(classname, updater_via)
|
75
|
-
elsif updater_via.is_a? Hash
|
76
|
-
klass = reflected_klass(classname, updater_via.keys.first)
|
77
|
-
updater_via.values.first.is_a?(Hash) ? search_base_klass(klass, updater_via.values.first) : reflected_klass(klass, updater_via.values.first)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def reflected_klass(classname, key)
|
82
|
-
klass = classname.constantize.reflect_on_all_associations.map { |assoc| [assoc.name, assoc.klass.name] }.to_h[key]
|
83
|
-
raise "Could not find reflected klass for classname '#{classname}' and key '#{key}' while setting sequence_attribute" unless klass
|
84
|
-
klass
|
85
|
-
end
|
86
|
-
|
87
|
-
def search_inverse_assoc(klass_object, updater_on)
|
88
|
-
if updater_on.values.first.is_a?(Hash)
|
89
|
-
klass_object = klass_object.public_send(updater_on.values.first.keys.first)
|
90
|
-
search_inverse_assoc(klass_object, updater_on.values.first)
|
91
|
-
else
|
92
|
-
klass_object.public_send(updater_on.values.first)
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
@@ -1,60 +0,0 @@
|
|
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
|