narabikae 0.2.0 → 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/README.md +104 -2
- data/lib/narabikae/active_record_extension.rb +32 -8
- data/lib/narabikae/configuration.rb +1 -1
- data/lib/narabikae/option.rb +10 -4
- data/lib/narabikae/position.rb +71 -30
- data/lib/narabikae/version.rb +1 -1
- data/lib/narabikae.rb +36 -6
- metadata +84 -18
- data/lib/narabikae/active_record_handler.rb +0 -31
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 889a0fb05988806d1ff5ab57e02932cf4bf8a57ad788e3a4f094456e15e57902
|
|
4
|
+
data.tar.gz: 1dfb405bc128aacd6bae61b89e133af0aa40cf67b16638ba475bb1933584c855
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0c2e99432062adc3745d8c6641d99bbd7a2b91a021103c552f3c4f9a7d6678370c344af81adf7d10a0ba3103730070bd8f7370fcb9c99a99dd06c8d01d89e882
|
|
7
|
+
data.tar.gz: 0aed46f7bf0ed8ae057324e17b99799d132e77244382ba0d945476221d3edb0d7c2f9444f2281b0d28a9e85e17fce9da1cb0eba13c27c613e4e4fe5da629bdaa
|
data/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Narabikae
|
|
2
2
|
|
|
3
|
+
[](https://github.com/kazu-2020/narabikae/actions/workflows/ci.yaml)
|
|
4
|
+
|
|
3
5
|
Narabikae(Japanese: 並び替え) means "reorder". Like [acts_as_list](https://github.com/brendon/acts_as_list), this gem provides automatic order management and reordering functionality for your records.
|
|
4
6
|
|
|
5
7
|
One of the key advantages of this gem is its use of the [fractional indexing algorithm](https://www.figma.com/blog/realtime-editing-of-ordered-sequences/#fractional-indexing), which greatly enhances the efficiency of reordering operations. With Narabikae, regardless of the amount of data, "only a single record" is updated during the reordering process 🎉.
|
|
@@ -67,6 +69,10 @@ class Task < ApplicationRecord
|
|
|
67
69
|
# Used for validation of the internally generated order value.
|
|
68
70
|
# This value should be equivalent to
|
|
69
71
|
# the limit set in the DB column.
|
|
72
|
+
#
|
|
73
|
+
# default_position: optional
|
|
74
|
+
# Set where new/auto-set records are inserted.
|
|
75
|
+
# Accepts :first or :last (default).
|
|
70
76
|
end
|
|
71
77
|
```
|
|
72
78
|
|
|
@@ -79,13 +85,23 @@ Task.create([
|
|
|
79
85
|
{ name: 'task-3' }
|
|
80
86
|
])
|
|
81
87
|
Task.order(:position).pluck(:name, :position)
|
|
82
|
-
# => [["task-1", "a0"], ["task-
|
|
88
|
+
# => [["task-1", "a0"], ["task-2", "a1"], ["task-3", "a2"]]
|
|
83
89
|
|
|
84
90
|
```
|
|
85
91
|
|
|
86
92
|
> [!NOTE]
|
|
87
93
|
> The position is set using the before_create callback. Therefore, do not define validations such as presence on the attributes managed by this gem!
|
|
88
94
|
|
|
95
|
+
#### Default position
|
|
96
|
+
|
|
97
|
+
By default, new/auto-set records are inserted at the end of the list. To insert at the beginning instead:
|
|
98
|
+
|
|
99
|
+
```rb
|
|
100
|
+
class Task < ApplicationRecord
|
|
101
|
+
narabikae :position, size: 200, default_position: :first
|
|
102
|
+
end
|
|
103
|
+
```
|
|
104
|
+
|
|
89
105
|
## Usage Details
|
|
90
106
|
|
|
91
107
|
### Reorder
|
|
@@ -155,6 +171,45 @@ target.position
|
|
|
155
171
|
# ex: target.move_to_position_between(tasks.first, nil)
|
|
156
172
|
```
|
|
157
173
|
|
|
174
|
+
### Set without saving
|
|
175
|
+
|
|
176
|
+
If you want to set the new position value and save later (for example, in a form), use `set_<field>_after/before/between`. These methods only assign the new position value and do not persist the record.
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
target.set_position_after(tasks.last)
|
|
180
|
+
target.position
|
|
181
|
+
# => 'a3'
|
|
182
|
+
target.save
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
You can also use setter-style aliases:
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
target.position_after = tasks.last
|
|
189
|
+
target.position_between = [tasks.first, tasks.last]
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
#### Form-friendly setters
|
|
193
|
+
|
|
194
|
+
The setter aliases can be used directly in forms or `assign_attributes`. They accept a target record or a position key (string). For `*_between=`, you can pass an array or hash. Primary key inputs are not accepted; do your own lookup and pass the record or its position.
|
|
195
|
+
|
|
196
|
+
```ruby
|
|
197
|
+
# position key input (e.g., from a hidden field)
|
|
198
|
+
task.assign_attributes(position_after: tasks.last.position)
|
|
199
|
+
|
|
200
|
+
# between using an array
|
|
201
|
+
task.position_between = [tasks.first, tasks.last]
|
|
202
|
+
|
|
203
|
+
# between using a hash (string or symbol keys)
|
|
204
|
+
task.position_between = { prev: tasks.first.position, next: tasks.last.position }
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
If you need retries, use the method form and pass `challenge` there:
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
task.set_position_between(tasks.first, tasks.last, challenge: 15)
|
|
211
|
+
```
|
|
212
|
+
|
|
158
213
|
### Scope
|
|
159
214
|
|
|
160
215
|
You can use this when you want to manage independent positions within specific scopes, such as foreign keys.
|
|
@@ -168,7 +223,7 @@ end
|
|
|
168
223
|
class Chapter < ApplicationRecord
|
|
169
224
|
belongs_to :course
|
|
170
225
|
|
|
171
|
-
narabikae :position, size: 100, scope:
|
|
226
|
+
narabikae :position, size: 100, scope: :course_id
|
|
172
227
|
end
|
|
173
228
|
|
|
174
229
|
course = Course.create
|
|
@@ -214,6 +269,53 @@ ticket.move_to_position_between(t1, t2, challenge: 15)
|
|
|
214
269
|
|
|
215
270
|
Feel free to message me on Github (kazu-2020)
|
|
216
271
|
|
|
272
|
+
## Development
|
|
273
|
+
|
|
274
|
+
### Supported versions (tested in CI)
|
|
275
|
+
|
|
276
|
+
- Ruby 3.1, 3.2, 3.3, 3.4, 4.0
|
|
277
|
+
- Rails 7.1, 7.2, 8.0, 8.1, and Rails main (via `railties` from `rails/rails`)
|
|
278
|
+
|
|
279
|
+
### Test suite
|
|
280
|
+
|
|
281
|
+
Tests are Minitest-based and run against a dummy Rails app located at `test/dummy`.
|
|
282
|
+
|
|
283
|
+
Database targets:
|
|
284
|
+
|
|
285
|
+
- `TARGET_DB=mysql` (default)
|
|
286
|
+
- `TARGET_DB=postgres`
|
|
287
|
+
- `TARGET_DB=sqlite`
|
|
288
|
+
|
|
289
|
+
To spin up database services locally:
|
|
290
|
+
|
|
291
|
+
```sh
|
|
292
|
+
docker compose up -d
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Run the full matrix locally (all databases):
|
|
296
|
+
|
|
297
|
+
```sh
|
|
298
|
+
bundle exec rake test
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Run a single database:
|
|
302
|
+
|
|
303
|
+
```sh
|
|
304
|
+
bundle exec rake test:postgres
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Run tests directly with Rails for a specific target:
|
|
308
|
+
|
|
309
|
+
```sh
|
|
310
|
+
TARGET_DB=sqlite bin/rails test
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
To test against a specific Rails version:
|
|
314
|
+
|
|
315
|
+
```sh
|
|
316
|
+
BUNDLE_GEMFILE=gemfiles/rails_8_1.gemfile TARGET_DB=mysql bundle exec rake test
|
|
317
|
+
```
|
|
318
|
+
|
|
217
319
|
## Contributing
|
|
218
320
|
|
|
219
321
|
Please wait a moment... 🙏
|
|
@@ -11,36 +11,60 @@ module Narabikae
|
|
|
11
11
|
# check valid key for fractional_indexer
|
|
12
12
|
# when invalid key, raise FractionalIndexer::Error
|
|
13
13
|
FractionalIndexer.generate_key(prev_key: record.send(option.field))
|
|
14
|
-
|
|
14
|
+
record.send(option.field).nil? ||
|
|
15
|
+
(option.scope.any? { |s| record.will_save_change_to_attribute?(s) } && !record.will_save_change_to_attribute?(option.field))
|
|
15
16
|
rescue FractionalIndexer::Error
|
|
16
17
|
true
|
|
17
18
|
end
|
|
18
19
|
|
|
19
|
-
def set_position
|
|
20
|
-
|
|
20
|
+
def set_position(position = option.default_position)
|
|
21
|
+
new_position =
|
|
22
|
+
case position
|
|
23
|
+
when :first
|
|
24
|
+
position_generator.create_first_position
|
|
25
|
+
when :last
|
|
26
|
+
position_generator.create_last_position
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
record.send("#{option.field}=", new_position)
|
|
21
30
|
end
|
|
22
31
|
|
|
23
|
-
def
|
|
32
|
+
def set_after(target, **args)
|
|
24
33
|
new_position = position_generator.find_position_after(target, **args)
|
|
25
34
|
return false if new_position.blank?
|
|
26
35
|
|
|
27
36
|
record.send("#{option.field}=", new_position)
|
|
28
|
-
record.save
|
|
29
37
|
end
|
|
30
38
|
|
|
31
|
-
def
|
|
39
|
+
def set_before(target, **args)
|
|
32
40
|
new_position = position_generator.find_position_before(target, **args)
|
|
33
41
|
return false if new_position.blank?
|
|
34
42
|
|
|
35
43
|
record.send("#{option.field}=", new_position)
|
|
36
|
-
record.save
|
|
37
44
|
end
|
|
38
45
|
|
|
39
|
-
def
|
|
46
|
+
def set_between(prev_target, next_target, **args)
|
|
40
47
|
new_position = position_generator.find_position_between(prev_target, next_target, **args)
|
|
41
48
|
return false if new_position.blank?
|
|
42
49
|
|
|
43
50
|
record.send("#{option.field}=", new_position)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def move_to_after(target, **args)
|
|
54
|
+
return false unless set_after(target, **args)
|
|
55
|
+
|
|
56
|
+
record.save
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def move_to_before(target, **args)
|
|
60
|
+
return false unless set_before(target, **args)
|
|
61
|
+
|
|
62
|
+
record.save
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def move_to_between(prev_target, next_target, **args)
|
|
66
|
+
return false unless set_between(prev_target, next_target, **args)
|
|
67
|
+
|
|
44
68
|
record.save
|
|
45
69
|
end
|
|
46
70
|
|
|
@@ -2,7 +2,7 @@ module Narabikae
|
|
|
2
2
|
class Configuration
|
|
3
3
|
# Sets the base value for FractionalIndexer configuration.
|
|
4
4
|
#
|
|
5
|
-
# @param int [Integer] The base value can be 10, 62, 94, with the default being
|
|
5
|
+
# @param int [Integer] The base value can be 10, 62, 94, with the default being 62.
|
|
6
6
|
# @return [void]
|
|
7
7
|
def base=(int)
|
|
8
8
|
FractionalIndexer.configure do |config|
|
data/lib/narabikae/option.rb
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
module Narabikae
|
|
2
2
|
class Option
|
|
3
|
-
attr_reader :field, :key_max_size, :scope
|
|
3
|
+
attr_reader :field, :key_max_size, :scope, :default_position
|
|
4
4
|
|
|
5
5
|
# Initializes a new instance of the Option class.
|
|
6
6
|
#
|
|
7
7
|
# @param field [Symbol]
|
|
8
8
|
# @param key_max_size [Integer] The maximum size of the key.
|
|
9
|
-
# @param scope [Array<Symbol>] The scope of the option.
|
|
10
|
-
|
|
9
|
+
# @param scope [Symbol, Array<Symbol>] The scope of the option.
|
|
10
|
+
# @param default_position [Symbol] The default position when creating or auto setting.
|
|
11
|
+
def initialize(field:, key_max_size:, scope: [], default_position: :last)
|
|
11
12
|
@field = field.to_sym
|
|
12
13
|
@key_max_size = key_max_size.to_i
|
|
13
|
-
@scope =
|
|
14
|
+
@scope = Array.wrap(scope).map(&:to_sym)
|
|
15
|
+
@default_position = (default_position || :last).to_sym
|
|
16
|
+
|
|
17
|
+
unless %i[first last].include?(@default_position)
|
|
18
|
+
raise ArgumentError, "default_position must be :first or :last"
|
|
19
|
+
end
|
|
14
20
|
end
|
|
15
21
|
end
|
|
16
22
|
end
|
data/lib/narabikae/position.rb
CHANGED
|
@@ -16,24 +16,29 @@ module Narabikae
|
|
|
16
16
|
FractionalIndexer.generate_key(prev_key: current_last_position)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
# Generates a new key for the first position
|
|
20
|
+
#
|
|
21
|
+
# @return [String] The newly generated key for the first position.
|
|
22
|
+
def create_first_position
|
|
23
|
+
FractionalIndexer.generate_key(next_key: current_first_position)
|
|
24
|
+
end
|
|
25
|
+
|
|
19
26
|
# Finds the position after the specified target.
|
|
20
27
|
# If generated key is invalid(ex: it already exists),
|
|
21
28
|
# a new key is generated until the challenge count reaches the limit.
|
|
22
29
|
# challenge count is 10 by default.
|
|
23
30
|
#
|
|
24
|
-
# @param target [
|
|
25
|
-
# @param
|
|
26
|
-
# @option args [Integer] :challenge The number of times to attempt finding a valid position.
|
|
31
|
+
# @param target [ActiveRecord::Base, String]
|
|
32
|
+
# @param challenge [Integer] The number of times to attempt finding a valid position.
|
|
27
33
|
# @return [String, nil] The generated key for the position after the target, or nil if no valid position is found.
|
|
28
|
-
def find_position_after(target,
|
|
29
|
-
merged_args = { challenge: 10 }.merge(args)
|
|
34
|
+
def find_position_after(target, challenge: 10)
|
|
30
35
|
# when target is nil, try to generate key from the last position
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
)
|
|
36
|
+
target_key = extract_target_key(target) || current_last_position
|
|
37
|
+
key = FractionalIndexer.generate_key(prev_key: target_key)
|
|
34
38
|
return key if valid?(key)
|
|
35
39
|
|
|
36
|
-
(
|
|
40
|
+
(challenge || 0).times do |i|
|
|
41
|
+
key = FractionalIndexer.generate_key(prev_key: target_key, next_key: key)
|
|
37
42
|
key += random_fractional
|
|
38
43
|
return key if valid?(key)
|
|
39
44
|
end
|
|
@@ -53,19 +58,17 @@ module Narabikae
|
|
|
53
58
|
# position = Position.new
|
|
54
59
|
# position.find_position_before(target, challenge: 5)
|
|
55
60
|
#
|
|
56
|
-
# @param target [
|
|
57
|
-
# @param
|
|
58
|
-
# @option args [Integer] :challenge The number of times to attempt finding a valid position.
|
|
61
|
+
# @param target [ActiveRecord::Base, String]
|
|
62
|
+
# @param challenge [Integer] The number of times to attempt finding a valid position.
|
|
59
63
|
# @return [String, nil] The generated key for the position before the target, or nil if no valid position is found.
|
|
60
|
-
def find_position_before(target,
|
|
61
|
-
merged_args = { challenge: 10 }.merge(args)
|
|
64
|
+
def find_position_before(target, challenge: 10)
|
|
62
65
|
# when target is nil, try to generate key from the first position
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
)
|
|
66
|
+
target_key = extract_target_key(target) || current_first_position
|
|
67
|
+
key = FractionalIndexer.generate_key(next_key: target_key)
|
|
66
68
|
return key if valid?(key)
|
|
67
69
|
|
|
68
|
-
(
|
|
70
|
+
(challenge || 0).times do |i|
|
|
71
|
+
key = FractionalIndexer.generate_key(prev_key: key, next_key: target_key)
|
|
69
72
|
key += random_fractional
|
|
70
73
|
return key if valid?(key)
|
|
71
74
|
end
|
|
@@ -77,25 +80,25 @@ module Narabikae
|
|
|
77
80
|
|
|
78
81
|
# Finds the position between two targets.
|
|
79
82
|
#
|
|
80
|
-
# @param prev_target [
|
|
81
|
-
# @param next_target [
|
|
82
|
-
# @param
|
|
83
|
-
# @option args [Integer] :challenge The number of times to attempt finding a valid position.
|
|
83
|
+
# @param prev_target [ActiveRecord::Base, String] The previous target.
|
|
84
|
+
# @param next_target [ActiveRecord::Base, String] The next target.
|
|
85
|
+
# @param challenge [Integer] The number of times to attempt finding a valid position.
|
|
84
86
|
# @return [string, nil] The position between the two targets, or nil if no valid position is found.
|
|
85
|
-
def find_position_between(prev_target, next_target,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
def find_position_between(prev_target, next_target, challenge: 10)
|
|
88
|
+
prev_key = extract_target_key(prev_target)
|
|
89
|
+
next_key = extract_target_key(next_target)
|
|
90
|
+
return find_position_before(next_target, challenge: challenge) if prev_key.blank?
|
|
91
|
+
return find_position_after(prev_target, challenge: challenge) if next_key.blank?
|
|
90
92
|
|
|
91
|
-
prev_key, next_key = [
|
|
93
|
+
prev_key, next_key = [ prev_key, next_key ].minmax
|
|
92
94
|
key = FractionalIndexer.generate_key(
|
|
93
95
|
prev_key: prev_key,
|
|
94
96
|
next_key: next_key,
|
|
95
97
|
)
|
|
96
98
|
return key if valid?(key)
|
|
97
99
|
|
|
98
|
-
(
|
|
100
|
+
(challenge || 0).times do |i|
|
|
101
|
+
key = FractionalIndexer.generate_key(prev_key: key, next_key: next_key)
|
|
99
102
|
key += random_fractional
|
|
100
103
|
return key if valid?(key)
|
|
101
104
|
end
|
|
@@ -122,7 +125,7 @@ module Narabikae
|
|
|
122
125
|
end
|
|
123
126
|
|
|
124
127
|
def model
|
|
125
|
-
record.class.base_class
|
|
128
|
+
record.class.base_class.unscoped
|
|
126
129
|
end
|
|
127
130
|
|
|
128
131
|
# generate a random fractional part
|
|
@@ -147,5 +150,43 @@ module Narabikae
|
|
|
147
150
|
|
|
148
151
|
capable?(key) && uniq?(key)
|
|
149
152
|
end
|
|
153
|
+
|
|
154
|
+
def extract_target_key(target)
|
|
155
|
+
return if target.nil?
|
|
156
|
+
return target if target.is_a?(String)
|
|
157
|
+
unless target.is_a?(ActiveRecord::Base)
|
|
158
|
+
raise Narabikae::Error, "target must be an ActiveRecord object or position key string"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
record_table = table_name_for_class(record)
|
|
162
|
+
target_table = table_name_for_class(target)
|
|
163
|
+
unless target_table && record_table && target_table == record_table
|
|
164
|
+
raise Narabikae::Error,
|
|
165
|
+
"target model mismatch: expected table #{record_table || 'unknown'}, got #{target_table || 'unknown'}"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
mismatched_columns = mismatched_scope_columns(target)
|
|
169
|
+
if mismatched_columns.any?
|
|
170
|
+
raise Narabikae::Error, "target scope mismatch for columns: #{mismatched_columns.join(', ')}"
|
|
171
|
+
end
|
|
172
|
+
raise Narabikae::Error, "target missing #{option.field} field" unless target.respond_to?(option.field)
|
|
173
|
+
|
|
174
|
+
target.send(option.field)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def table_name_for_class(value)
|
|
178
|
+
klass = value.class
|
|
179
|
+
return unless klass.respond_to?(:table_name)
|
|
180
|
+
|
|
181
|
+
klass.table_name
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def mismatched_scope_columns(target)
|
|
185
|
+
option.scope.select do |column|
|
|
186
|
+
!target.respond_to?(column) ||
|
|
187
|
+
!record.respond_to?(column) ||
|
|
188
|
+
target.public_send(column) != record.public_send(column)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
150
191
|
end
|
|
151
192
|
end
|
data/lib/narabikae/version.rb
CHANGED
data/lib/narabikae.rb
CHANGED
|
@@ -29,20 +29,50 @@ module Narabikae
|
|
|
29
29
|
extend ActiveSupport::Concern
|
|
30
30
|
|
|
31
31
|
class_methods do
|
|
32
|
-
def narabikae(field = :position, size:, scope: [])
|
|
32
|
+
def narabikae(field = :position, size:, scope: [], default_position: :last)
|
|
33
33
|
option = narabikae_option_store.register!(
|
|
34
34
|
field.to_sym,
|
|
35
|
-
Narabikae::Option.new(field: field, key_max_size: size, scope: scope)
|
|
35
|
+
Narabikae::Option.new(field: field, key_max_size: size, scope: scope, default_position: default_position)
|
|
36
36
|
)
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
before_save -> {
|
|
39
39
|
extension = Narabikae::ActiveRecordExtension.new(self, option)
|
|
40
|
-
extension.set_position
|
|
40
|
+
extension.set_position(option.default_position) if extension.auto_set_position?
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
define_method :"set_#{field}_after" do |target = nil, **args|
|
|
44
|
+
extension = Narabikae::ActiveRecordExtension.new(self, option)
|
|
45
|
+
extension.set_after(target, **args)
|
|
46
|
+
end
|
|
47
|
+
alias_method :"#{field}_after=", :"set_#{field}_after"
|
|
48
|
+
|
|
49
|
+
define_method :"set_#{field}_before" do |target = nil, **args|
|
|
50
|
+
extension = Narabikae::ActiveRecordExtension.new(self, option)
|
|
51
|
+
extension.set_before(target, **args)
|
|
52
|
+
end
|
|
53
|
+
alias_method :"#{field}_before=", :"set_#{field}_before"
|
|
54
|
+
|
|
55
|
+
define_method :"set_#{field}_between" do |prev_target = nil, next_target = nil, **args|
|
|
56
|
+
extension = Narabikae::ActiveRecordExtension.new(self, option)
|
|
57
|
+
extension.set_between(prev_target, next_target, **args)
|
|
41
58
|
end
|
|
59
|
+
define_method :"#{field}_between=" do |value|
|
|
60
|
+
prev_target = nil
|
|
61
|
+
next_target = nil
|
|
62
|
+
|
|
63
|
+
case value
|
|
64
|
+
when Array
|
|
65
|
+
prev_target, next_target = value
|
|
66
|
+
when Hash
|
|
67
|
+
payload = value.with_indifferent_access
|
|
68
|
+
prev_target = payload[:prev_target] || payload[:prev]
|
|
69
|
+
next_target = payload[:next_target] || payload[:next]
|
|
70
|
+
else
|
|
71
|
+
prev_target = value
|
|
72
|
+
end
|
|
42
73
|
|
|
43
|
-
before_update do
|
|
44
74
|
extension = Narabikae::ActiveRecordExtension.new(self, option)
|
|
45
|
-
extension.
|
|
75
|
+
extension.set_between(prev_target, next_target)
|
|
46
76
|
end
|
|
47
77
|
|
|
48
78
|
define_method :"move_to_#{field}_after" do |target = nil, **args|
|
metadata
CHANGED
|
@@ -1,37 +1,106 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: narabikae
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- matazou
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activerecord
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '7.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: railties
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '7.1'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '7.1'
|
|
13
40
|
- !ruby/object:Gem::Dependency
|
|
14
41
|
name: fractional_indexer
|
|
15
42
|
requirement: !ruby/object:Gem::Requirement
|
|
16
43
|
requirements:
|
|
17
44
|
- - ">="
|
|
18
45
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
46
|
+
version: 0.4.0
|
|
20
47
|
type: :runtime
|
|
21
48
|
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: 0.4.0
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: appraisal
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
22
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
64
|
requirements:
|
|
24
65
|
- - ">="
|
|
25
66
|
- !ruby/object:Gem::Version
|
|
26
67
|
version: '0'
|
|
27
68
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
69
|
+
name: debug
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '1.9'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '1.9'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: minitest
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '5.0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '5.0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: mocha
|
|
29
98
|
requirement: !ruby/object:Gem::Requirement
|
|
30
99
|
requirements:
|
|
31
100
|
- - ">="
|
|
32
101
|
- !ruby/object:Gem::Version
|
|
33
102
|
version: '0'
|
|
34
|
-
type: :
|
|
103
|
+
type: :development
|
|
35
104
|
prerelease: false
|
|
36
105
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
106
|
requirements:
|
|
@@ -39,13 +108,13 @@ dependencies:
|
|
|
39
108
|
- !ruby/object:Gem::Version
|
|
40
109
|
version: '0'
|
|
41
110
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
111
|
+
name: mysql2
|
|
43
112
|
requirement: !ruby/object:Gem::Requirement
|
|
44
113
|
requirements:
|
|
45
114
|
- - ">="
|
|
46
115
|
- !ruby/object:Gem::Version
|
|
47
116
|
version: '0'
|
|
48
|
-
type: :
|
|
117
|
+
type: :development
|
|
49
118
|
prerelease: false
|
|
50
119
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
120
|
requirements:
|
|
@@ -53,21 +122,21 @@ dependencies:
|
|
|
53
122
|
- !ruby/object:Gem::Version
|
|
54
123
|
version: '0'
|
|
55
124
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
125
|
+
name: pg
|
|
57
126
|
requirement: !ruby/object:Gem::Requirement
|
|
58
127
|
requirements:
|
|
59
128
|
- - ">="
|
|
60
129
|
- !ruby/object:Gem::Version
|
|
61
|
-
version:
|
|
130
|
+
version: '0'
|
|
62
131
|
type: :development
|
|
63
132
|
prerelease: false
|
|
64
133
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
134
|
requirements:
|
|
66
135
|
- - ">="
|
|
67
136
|
- !ruby/object:Gem::Version
|
|
68
|
-
version:
|
|
137
|
+
version: '0'
|
|
69
138
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name:
|
|
139
|
+
name: sqlite3
|
|
71
140
|
requirement: !ruby/object:Gem::Requirement
|
|
72
141
|
requirements:
|
|
73
142
|
- - ">="
|
|
@@ -81,7 +150,7 @@ dependencies:
|
|
|
81
150
|
- !ruby/object:Gem::Version
|
|
82
151
|
version: '0'
|
|
83
152
|
- !ruby/object:Gem::Dependency
|
|
84
|
-
name: rubocop-
|
|
153
|
+
name: rubocop-rails-omakase
|
|
85
154
|
requirement: !ruby/object:Gem::Requirement
|
|
86
155
|
requirements:
|
|
87
156
|
- - ">="
|
|
@@ -109,7 +178,6 @@ files:
|
|
|
109
178
|
- README.md
|
|
110
179
|
- lib/narabikae.rb
|
|
111
180
|
- lib/narabikae/active_record_extension.rb
|
|
112
|
-
- lib/narabikae/active_record_handler.rb
|
|
113
181
|
- lib/narabikae/configuration.rb
|
|
114
182
|
- lib/narabikae/option.rb
|
|
115
183
|
- lib/narabikae/option_store.rb
|
|
@@ -125,7 +193,6 @@ metadata:
|
|
|
125
193
|
source_code_uri: https://github.com/kazu-2020/narabikae
|
|
126
194
|
allowed_push_host: https://rubygems.org/
|
|
127
195
|
rubygems_mfa_required: 'true'
|
|
128
|
-
post_install_message:
|
|
129
196
|
rdoc_options: []
|
|
130
197
|
require_paths:
|
|
131
198
|
- lib
|
|
@@ -133,15 +200,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
133
200
|
requirements:
|
|
134
201
|
- - ">="
|
|
135
202
|
- !ruby/object:Gem::Version
|
|
136
|
-
version: '
|
|
203
|
+
version: '3.1'
|
|
137
204
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
138
205
|
requirements:
|
|
139
206
|
- - ">="
|
|
140
207
|
- !ruby/object:Gem::Version
|
|
141
208
|
version: '0'
|
|
142
209
|
requirements: []
|
|
143
|
-
rubygems_version: 3.
|
|
144
|
-
signing_key:
|
|
210
|
+
rubygems_version: 3.6.7
|
|
145
211
|
specification_version: 4
|
|
146
212
|
summary: provides simple position management and sorting functionality for Active
|
|
147
213
|
Record in Rails.
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
module Narabikae
|
|
2
|
-
class ActiveRecordHandler
|
|
3
|
-
# Initializes a new instance of the ActiveRecordHandler class.
|
|
4
|
-
#
|
|
5
|
-
# @param record [Object] The ActiveRecord object.
|
|
6
|
-
# @param column [Symbol] The column symbol.
|
|
7
|
-
def initialize(record, column)
|
|
8
|
-
@record = record
|
|
9
|
-
@column = column
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
# Generates a new key for the last position
|
|
13
|
-
#
|
|
14
|
-
# @return [String] The newly generated key for the last position.
|
|
15
|
-
def create_last_position
|
|
16
|
-
FractionalIndexer.generate_key(prev_key: current_last_position)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
private
|
|
20
|
-
|
|
21
|
-
attr_reader :record, :column
|
|
22
|
-
|
|
23
|
-
def current_last_position
|
|
24
|
-
model.maximum(column)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def model
|
|
28
|
-
record.class.base_class
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|