lexorank 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +21 -0
- data/lib/lexorank.rb +82 -0
- data/lib/lexorank/rankable.rb +92 -0
- data/lib/lexorank/version.rb +3 -0
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 016c148a28989a35ab6afd0184625900cbec71dac5dcfa981cac45403c9aa0d5
|
4
|
+
data.tar.gz: 46cfe89fa41fb20b511235122b8511702ddfba532c1d475d7dfe2cec337a2e7c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 969a110cb1cc3c60ea599b6b747326a6f86c57de0a261c0f369d0691108c59f757e8d0d12b2d669bc2dbfefe275dd83e1d851f6f6b181ac1dec917388f54458c
|
7
|
+
data.tar.gz: 249348edc5ffeb92cc42a1d98e585c671560443d53e5a150b64d5674f49ceaf7857e3ca80208a8da0de20ca380aa5598fa5726672842c833294d2710d4a68e4a
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 Richard Böhme
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/lib/lexorank.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'lexorank/version'
|
2
|
+
|
3
|
+
# Inspired by https://github.com/DevStarSJ/LexoRank/blob/master/lexo_rank.rb licensed under
|
4
|
+
# MIT License
|
5
|
+
#
|
6
|
+
# Copyright (c) 2019 SeokJoon.Yun
|
7
|
+
#
|
8
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
9
|
+
# of this software and associated documentation files (the "Software"), to deal
|
10
|
+
# in the Software without restriction, including without limitation the rights
|
11
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
12
|
+
# copies of the Software, and to permit persons to whom the Software is
|
13
|
+
# furnished to do so, subject to the following conditions:
|
14
|
+
#
|
15
|
+
# The above copyright notice and this permission notice shall be included in all
|
16
|
+
# copies or substantial portions of the Software.
|
17
|
+
#
|
18
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
19
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
20
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
21
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
22
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
23
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
24
|
+
# SOFTWARE.
|
25
|
+
module Lexorank
|
26
|
+
class InvalidRankError < StandardError; end
|
27
|
+
|
28
|
+
MIN_CHAR = '0'.freeze
|
29
|
+
MAX_CHAR = 'z'.freeze
|
30
|
+
|
31
|
+
def value_between(_before_, _after_)
|
32
|
+
before = _before_ || MIN_CHAR
|
33
|
+
after = _after_ || MAX_CHAR
|
34
|
+
|
35
|
+
rank = ''
|
36
|
+
|
37
|
+
(before.length + after.length).times do |i|
|
38
|
+
prev_char = get_char(before, i, MIN_CHAR)
|
39
|
+
after_char = get_char(after, i, MAX_CHAR)
|
40
|
+
|
41
|
+
if prev_char == after_char
|
42
|
+
rank += prev_char
|
43
|
+
next
|
44
|
+
end
|
45
|
+
|
46
|
+
mid_char = mid(prev_char, after_char)
|
47
|
+
if mid_char == prev_char || mid_char == after_char
|
48
|
+
rank += prev_char
|
49
|
+
next
|
50
|
+
end
|
51
|
+
|
52
|
+
rank += mid_char
|
53
|
+
break
|
54
|
+
end
|
55
|
+
|
56
|
+
# Problem: If we try to get a rank before the character '0' or after 'z' the algorithm would return the same char
|
57
|
+
# This first of all breaks a possible unique constraint and of course makes no sense when ordering the items.
|
58
|
+
#
|
59
|
+
# Thoughts: I think this issue will never happen with the Lexorank::Rankable module
|
60
|
+
# Why? Let's look at '0' as a rank:
|
61
|
+
# Because the algorithm always chooses the char in between two other chars, '0' can only happen when before is nil and after is '1'
|
62
|
+
# In this case the algorithm will return '0U' though. This means there will never be an item with rank '0' which is why this condition
|
63
|
+
# should never equal to true.
|
64
|
+
#
|
65
|
+
# Please report if you have another opinion about that or if you reached the exception! (of course you can force it by using `value_between(nil, '0')`)
|
66
|
+
if rank >= after
|
67
|
+
raise InvalidRankError, "This rank should not be achievable using the Lexorank::Rankable module! Please report to https://github.com/richardboehme/lexorank/issues! " +
|
68
|
+
"The supplied ranks were #{_before_.inspect} and #{_after_.inspect}. Please include those in the issue description."
|
69
|
+
end
|
70
|
+
rank
|
71
|
+
end
|
72
|
+
|
73
|
+
def mid(prev, after)
|
74
|
+
middle_ascii = ((prev.ord + after.ord) / 2).round
|
75
|
+
middle_ascii.chr
|
76
|
+
end
|
77
|
+
|
78
|
+
def get_char(str, i, default_char)
|
79
|
+
i >= str.length ? default_char : str[i]
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'lexorank'
|
2
|
+
require 'active_support/concern'
|
3
|
+
|
4
|
+
module Lexorank::Rankable
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
attr_reader :ranking_column, :ranking_group_by
|
9
|
+
|
10
|
+
def rank!(field: :rank, group_by: nil)
|
11
|
+
@ranking_column = check_column(field)
|
12
|
+
if group_by
|
13
|
+
@ranking_group_by = check_column(group_by)
|
14
|
+
unless @ranking_group_by
|
15
|
+
warn "The supplied grouping by \"#{group_by}\" is neither a column nor an association of the model!"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
if @ranking_column
|
20
|
+
self.scope :ranked, ->(direction: :asc) { where.not("#{field}": nil).order("#{field}": direction) }
|
21
|
+
self.include Lexorank
|
22
|
+
self.include InstanceMethods
|
23
|
+
else
|
24
|
+
warn "The supplied ranking column \"#{field}\" is not a column of the model!"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def check_column(column_name)
|
31
|
+
return unless column_name
|
32
|
+
# This requires an active connection... do we want this?
|
33
|
+
if self.columns.map(&:name).include?(column_name.to_s)
|
34
|
+
column_name
|
35
|
+
# This requires rank! to be after the specific association
|
36
|
+
elsif (association = self.reflect_on_association(column_name))
|
37
|
+
association.foreign_key.to_sym
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
module InstanceMethods
|
44
|
+
def move_to_top
|
45
|
+
move_to(0)
|
46
|
+
end
|
47
|
+
|
48
|
+
def move_to(position)
|
49
|
+
collection = self.class.ranked
|
50
|
+
if self.class.ranking_group_by.present?
|
51
|
+
collection = collection.where("#{self.class.ranking_group_by}": send(self.class.ranking_group_by))
|
52
|
+
end
|
53
|
+
|
54
|
+
# exceptions:
|
55
|
+
# move to the beginning (aka move to position 0)
|
56
|
+
# move to end (aka position = collection.size - 1)
|
57
|
+
# when moving to the end of the collection the offset and limit statement automatically handles
|
58
|
+
# that 'after' is nil which is the same like [collection.last, nil]
|
59
|
+
before, after =
|
60
|
+
if position == 0
|
61
|
+
[nil, collection.first]
|
62
|
+
else
|
63
|
+
# if item is currently in front of the index we just use position otherwise position - 1
|
64
|
+
# if the item has no rank we use position - 1
|
65
|
+
position -= 1 if !self.send(self.class.ranking_column) || collection.map(&:id).index(self.id) > position
|
66
|
+
collection.offset(position).limit(2)
|
67
|
+
end
|
68
|
+
|
69
|
+
rank =
|
70
|
+
if self == after && self.send(self.class.ranking_column).present?
|
71
|
+
self.send(self.class.ranking_column)
|
72
|
+
else
|
73
|
+
value_between(before&.send(self.class.ranking_column), after&.send(self.class.ranking_column))
|
74
|
+
end
|
75
|
+
|
76
|
+
self.send("#{self.class.ranking_column}=", rank)
|
77
|
+
end
|
78
|
+
|
79
|
+
def move_to!(position)
|
80
|
+
move_to(position)
|
81
|
+
save
|
82
|
+
end
|
83
|
+
|
84
|
+
def move_to_top!
|
85
|
+
move_to!(0)
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
ActiveRecord::Base.send(:include, Lexorank::Rankable)
|
92
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lexorank
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richard Böhme
|
@@ -142,7 +142,11 @@ email:
|
|
142
142
|
executables: []
|
143
143
|
extensions: []
|
144
144
|
extra_rdoc_files: []
|
145
|
-
files:
|
145
|
+
files:
|
146
|
+
- LICENSE
|
147
|
+
- lib/lexorank.rb
|
148
|
+
- lib/lexorank/rankable.rb
|
149
|
+
- lib/lexorank/version.rb
|
146
150
|
homepage: https://github.com/richardboehme/lexorank
|
147
151
|
licenses:
|
148
152
|
- MIT
|
@@ -162,7 +166,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
162
166
|
- !ruby/object:Gem::Version
|
163
167
|
version: '0'
|
164
168
|
requirements: []
|
165
|
-
rubygems_version: 3.
|
169
|
+
rubygems_version: 3.2.3
|
166
170
|
signing_key:
|
167
171
|
specification_version: 4
|
168
172
|
summary: Store order of your models by using lexicographic sorting.
|