activekit 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|