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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c8c678d7cfe9046d4eeeb30c899a105ba37de5eececb64ff7b34447e051a8f23
4
- data.tar.gz: 36ed27c5cc7603494de3f096619f21bc603b152f8c1311ad894ac77dffc95b0b
3
+ metadata.gz: 016c148a28989a35ab6afd0184625900cbec71dac5dcfa981cac45403c9aa0d5
4
+ data.tar.gz: 46cfe89fa41fb20b511235122b8511702ddfba532c1d475d7dfe2cec337a2e7c
5
5
  SHA512:
6
- metadata.gz: 17531cc2661b9df6803957659dd97451e66885502504c345a7f54301f5ef78f65f9f28bba59745109f4820e5b21f26ac3c11ab77e558559edcbd94dd18be2845
7
- data.tar.gz: cbb64e16863bb7f587f09f5b30b993712a68214b2b812a8bee87f29e97800955f429a8923c6dff6632a710b0261d74945ab1d1b34abec4c9f2c202c6a198d163
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
+
@@ -0,0 +1,3 @@
1
+ module Lexorank
2
+ VERSION = '0.1.2'
3
+ end
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.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.1.2
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.