narabikae 0.1.0 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 64ba8405e3f4412d5c417fb13a499653d883308f93aa704350eedfc3e70dc1f9
4
- data.tar.gz: cc4217ce041d2db2e41af69182427fc6865dffed4329f23f5b70f0a1e8a26860
3
+ metadata.gz: 07a44a8622d1ba78a90fa807d5947713bb47fdff0cb644973d8a5657ce37d453
4
+ data.tar.gz: 17dc1892a77e52ea46b6753cd621d5a0fb9f45ffd2c1ab0adbbac138063437ed
5
5
  SHA512:
6
- metadata.gz: bd0676a4413917e858801f4ec5a99c210ba82a92a77fb7153b5b20e2bff2a40f8ce1c13ebc34b067c863d2d5fe56a5806a521553850295a2a62a363b1e3543bf
7
- data.tar.gz: af54f6713a9f547a46c99393d6ede9198ced3ce47fdf3eb48e776482b3aa66d2b02f0bcdf218b9dbb8d478db4bee0332e9b56f6a5cb296372d0c70b203384e7f
6
+ metadata.gz: 3a459cabd5079dd5c308726e40ad42fdcbe082e6b266e94220d27cf336f3b62da6d5aba39df39c443079533509a31c20ff7b5ebffca2bfc25dece8e340f6ef7f
7
+ data.tar.gz: 9875163ef2ef2ffb5f48ffde782883b215875c498ef214027be373a88ac6789f8f172db6480ac50eb4e44804da4b9cdc1bfacefcbadfef57d5f41c2b5c1558f5
data/README.md CHANGED
@@ -1,28 +1,225 @@
1
1
  # Narabikae
2
- Short description and motivation.
3
2
 
4
- ## Usage
5
- How to use my plugin.
3
+ [![test](https://github.com/kazu-2020/narabikae/actions/workflows/ci.yaml/badge.svg?branch=main&event=push)](https://github.com/kazu-2020/narabikae/actions/workflows/ci.yaml)
4
+
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.
6
+
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 🎉.
6
8
 
7
9
  ## Installation
8
- Add this line to your application's Gemfile:
10
+
11
+ In your Gemfile
9
12
 
10
13
  ```ruby
11
14
  gem "narabikae"
12
15
  ```
13
16
 
14
- And then execute:
15
- ```bash
16
- $ bundle
17
+ ## Getting started
18
+
19
+ ### Adding a column to manage order
20
+
21
+ To manage the order of records, you'll need to create a column in your database. A key feature of this gem is that it generates the order as a string!
22
+
23
+ ```rb
24
+ # example
25
+
26
+ create_table :tasks do |t|
27
+ t.string :name
28
+
29
+ # for MySQL
30
+ t.string :position, null: false, limit: 200, charset: 'ascii', collation: 'ascii_bin'
31
+
32
+ # for PostgreSQL
33
+ t.string :position, null: false, limit: 200, collation: 'C'
34
+
35
+ # for SQLite3
36
+ t.string :position, null: false, collation: 'binary'
37
+ end
38
+ add_index :tasks, :position, unique: true
39
+
40
+ ```
41
+
42
+ #### Key points to consider when creating the column:
43
+
44
+ - Set the collation to distinguish between uppercase and lowercase letters.
45
+
46
+ For example, if using MySQL 8.0’s default collation (utf8mb4_0900_ai_ci), which does not distinguish between uppercase and lowercase, the sort results may not behave as expected.
47
+
48
+ - It is recommended to apply both NOT NULL and UNIQUE constraints.
49
+
50
+ This ensures data integrity and efficient ordering.
51
+
52
+ - Explicitly set a character limit for the column.
53
+
54
+ Since this column will typically be indexed, it is important to set an appropriate length. This gem uses a base-62 numbering system to represent the order. In the example above, with a length limit of 200 characters, you can represent up to "62^200 unique order values", providing a huge range for ordered sequences.
55
+
56
+ ### Adding configuration to your model
57
+
58
+ You only need to add one line as shown below!
59
+
60
+ ```rb
61
+ class Task < ApplicationRecord
62
+ narabikae :position, size: 200
63
+
64
+ # arg1: optional
65
+ # . Specify the field you want to use for ordering.
66
+ # The default is :position.
67
+ #
68
+ # size: required
69
+ # Used for validation of the internally generated order value.
70
+ # This value should be equivalent to
71
+ # the limit set in the DB column.
72
+ end
73
+ ```
74
+
75
+ Once this is done, the position will be automatically set each time a Task model instance is saved!
76
+
77
+ ```rb
78
+ Task.create([
79
+ { name: 'task-1' },
80
+ { name: 'task-2' },
81
+ { name: 'task-3' }
82
+ ])
83
+ Task.order(:position).pluck(:name, :position)
84
+ # => [["task-1", "a0"], ["task-2", "a1"], ["task-3", "a2"]]
85
+
86
+ ```
87
+
88
+ > [!NOTE]
89
+ > The position is set using the before_create callback. Therefore, do not define validations such as presence on the attributes managed by this gem!
90
+
91
+ ## Usage Details
92
+
93
+ ### Reorder
94
+
95
+ To insert an element after any specified item, use the `move_to_<field>_after` method.
96
+
97
+ ```ruby
98
+ target = Task.create(name: 'target') # pos: 'a0'
99
+ tasks = Task.create([
100
+ { name: 'task-1' }, # pos: 'a1'
101
+ { name: 'task-2' } # pos: 'a2'
102
+ ])
103
+
104
+ target.move_to_position_after(tasks.last)
105
+ # => true
106
+ target.position
107
+ # => 'a3'
108
+
109
+ # If no argument is passed, it will be inserted at the end of the list
110
+ tasks.first.move_to_position_after
111
+ # => true
112
+
113
+ Task.order(:position).pluck(:name, :position)
114
+ # => [["task-2", "a2"], ["target", "a3"], ["task-1", "a4"]]
115
+ ```
116
+
117
+ To insert an element before any specified item, use the `move_to_<field>_before` method.
118
+
119
+ ```ruby
120
+ tasks = Task.create([
121
+ { name: 'task-1' }, # pos: 'a0'
122
+ { name: 'task-2' } # pos: 'a1'
123
+ ])
124
+ target = Task.create(name: 'target') # pos: 'a2'
125
+
126
+ target.move_to_position_before(tasks.first)
127
+ target.position
128
+ # => 'Zz'
129
+
130
+ # If no argument is passed, it will be inserted at the start of the list
131
+ tasks.last.move_to_position_before
132
+ # => true
133
+
134
+ Task.order(:position).pluck(:name, :position)
135
+ # => [["task-2", "Zy"], ["target", "Zz"], ["task-1", "a0"]]
17
136
  ```
18
137
 
19
- Or install it yourself as:
20
- ```bash
21
- $ gem install narabikae
138
+ The method you will likely use most often is `move_to_<field>_between`, which moves an element between two others!
139
+
140
+ ```ruby
141
+ tasks = Task.create([
142
+ { name: 'task-1' }, # pos: 'a0'
143
+ { name: 'task-2' } # pos: 'a1'
144
+ ])
145
+ target = Task.create(name: 'target') # pos: 'a2'
146
+
147
+ target.move_to_position_between(tasks.first, tasks.last)
148
+ # => true
149
+
150
+ target.position
151
+ # => 'a0V'
152
+
153
+ # If the first argument is nil, it behaves the same as `move_to_<field>_before`
154
+ # ex: target.move_to_position_between(nil, tasks.last)
155
+
156
+ # If the second argument is nil, it behaves the same as `move_to_<field>_after`
157
+ # ex: target.move_to_position_between(tasks.first, nil)
22
158
  ```
23
159
 
160
+ ### Scope
161
+
162
+ You can use this when you want to manage independent positions within specific scopes, such as foreign keys.
163
+
164
+ ```ruby
165
+ # example
166
+ class Course < ApplicationRecord
167
+ has_many :chapters, dependent: :destroy
168
+ end
169
+
170
+ class Chapter < ApplicationRecord
171
+ belongs_to :course
172
+
173
+ narabikae :position, size: 100, scope: %i[course_id]
174
+ end
175
+
176
+ course = Course.create
177
+ other_course = Course.create
178
+
179
+ course.chapters.create
180
+ other_course.chapters.create
181
+
182
+ Chapter.pluck(:course_id, :position)
183
+ # => [[1, "a0"], [2, "a0"]]
184
+ ```
185
+
186
+ When the attribute declared in the scope is changed during an update, and there is no change in the value of the position field, the position will be automatically recalculated, and the record will move to the end of the list.
187
+
188
+ ```ruby
189
+ course = Course.create
190
+ other_course = Course.create
191
+
192
+ course.chapters.create
193
+ chapter = course.chapters.create # pos: 'a1'
194
+
195
+ chapter.course = other_course
196
+ chapter.save
197
+
198
+ chapter.position
199
+ # => 'a0'
200
+ ```
201
+
202
+ ### Retry generating position
203
+
204
+ Imagine the drag-and-drop functionality found in GitHub Projects or Zenhub, where items can be reordered. If two users try to insert different tickets between the same two tickets at the same time, their positions may overlap. In this case, the system will internally retry generating a unique position (up to 10 times by default).
205
+
206
+ If you want to increase the number of retries, you can use the "challenge" option to control the retry attempts, as shown below:
207
+
208
+ ```ruby
209
+ ticket.move_to_position_between(t1, t2, challenge: 15)
210
+ ```
211
+
212
+ > [!NOTE]
213
+ > Currently, if two users write to the database at exactly the same time, the system may not detect the duplicate positions, and the process will continue without error. This issue will be revisited if there is demand for a more robust solution in the future.
214
+
215
+ ## Questions, Feedback
216
+
217
+ Feel free to message me on Github (kazu-2020)
218
+
24
219
  ## Contributing
25
- Contribution directions go here.
220
+
221
+ Please wait a moment... 🙏
26
222
 
27
223
  ## License
224
+
28
225
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,51 @@
1
+ module Narabikae
2
+ class ActiveRecordExtension
3
+ def initialize(record, option)
4
+ @record = record
5
+ @option = option
6
+
7
+ @position_generator = Narabikae::Position.new(record, option)
8
+ end
9
+
10
+ def auto_set_position?
11
+ # check valid key for fractional_indexer
12
+ # when invalid key, raise FractionalIndexer::Error
13
+ FractionalIndexer.generate_key(prev_key: record.send(option.field))
14
+ option.scope.any? { |s| record.will_save_change_to_attribute?(s) } && !record.will_save_change_to_attribute?(option.field)
15
+ rescue FractionalIndexer::Error
16
+ true
17
+ end
18
+
19
+ def set_position
20
+ record.send("#{option.field}=", position_generator.create_last_position)
21
+ end
22
+
23
+ def move_to_after(target, **args)
24
+ new_position = position_generator.find_position_after(target, **args)
25
+ return false if new_position.blank?
26
+
27
+ record.send("#{option.field}=", new_position)
28
+ record.save
29
+ end
30
+
31
+ def move_to_before(target, **args)
32
+ new_position = position_generator.find_position_before(target, **args)
33
+ return false if new_position.blank?
34
+
35
+ record.send("#{option.field}=", new_position)
36
+ record.save
37
+ end
38
+
39
+ def move_to_between(prev_target, next_target, **args)
40
+ new_position = position_generator.find_position_between(prev_target, next_target, **args)
41
+ return false if new_position.blank?
42
+
43
+ record.send("#{option.field}=", new_position)
44
+ record.save
45
+ end
46
+
47
+ private
48
+
49
+ attr_reader :record, :option, :position_generator
50
+ end
51
+ end
@@ -0,0 +1,31 @@
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
@@ -0,0 +1,19 @@
1
+ module Narabikae
2
+ class Configuration
3
+ # Sets the base value for FractionalIndexer configuration.
4
+ #
5
+ # @param int [Integer] The base value can be 10, 62, 94, with the default being 94.
6
+ # @return [void]
7
+ def base=(int)
8
+ FractionalIndexer.configure do |config|
9
+ config.base = "base_#{int}".to_sym
10
+ end
11
+ end
12
+
13
+ # @return [Array] The string of digits configured for the FractionalIndexer.
14
+ # @see https://github.com/kazu-2020/fractional_indexer?tab=readme-ov-file#configure
15
+ def digits
16
+ FractionalIndexer.configuration.digits
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ module Narabikae
2
+ class Option
3
+ attr_reader :field, :key_max_size, :scope
4
+
5
+ # Initializes a new instance of the Option class.
6
+ #
7
+ # @param field [Symbol]
8
+ # @param key_max_size [Integer] The maximum size of the key.
9
+ # @param scope [Array<Symbol>] The scope of the option.
10
+ def initialize(field:, key_max_size:, scope: [])
11
+ @field = field.to_sym
12
+ @key_max_size = key_max_size.to_i
13
+ @scope = scope.is_a?(Array) ? scope.map(&:to_sym) : []
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,25 @@
1
+ module Narabikae
2
+ class OptionStore
3
+ attr_reader :store
4
+
5
+ def initialize
6
+ @store = {}
7
+ end
8
+
9
+ def register!(field, option)
10
+ if store.key?(field)
11
+ raise Narabikae::Error, "the field `#{field}` is already registered"
12
+ end
13
+ if option.scope.include?(field)
14
+ raise Narabikae::Error, "dependency loop detected: #{option.scope}"
15
+ end
16
+ if option.scope.any? { |s| store.key?(s) }
17
+ raise Narabikae::Error, "the scope `#{option.scope}` is already registered as other field"
18
+ end
19
+
20
+ store[field] = option
21
+
22
+ option
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,152 @@
1
+ module Narabikae
2
+ class Position
3
+ # Initializes a new instance of the Position class.
4
+ #
5
+ # @param record [Object] Active Record object.
6
+ # @param option [Option]
7
+ def initialize(record, option)
8
+ @record = record
9
+ @option = option
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
+ # Finds the position after the specified target.
20
+ # If generated key is invalid(ex: it already exists),
21
+ # a new key is generated until the challenge count reaches the limit.
22
+ # challenge count is 10 by default.
23
+ #
24
+ # @param target [#send(field)]
25
+ # @param args [Hash] Additional arguments.
26
+ # @option args [Integer] :challenge The number of times to attempt finding a valid position.
27
+ # @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, **args)
29
+ merged_args = { challenge: 10 }.merge(args)
30
+ # when target is nil, try to generate key from the last position
31
+ target_key = target&.send(option.field) || current_last_position
32
+ key = FractionalIndexer.generate_key(prev_key: target_key)
33
+ return key if valid?(key)
34
+
35
+ (merged_args[:challenge] || 0).times do |i|
36
+ key = FractionalIndexer.generate_key(prev_key: target_key, next_key: key)
37
+ key += random_fractional
38
+ return key if valid?(key)
39
+ end
40
+
41
+ nil
42
+ rescue FractionalIndexer::Error
43
+ nil
44
+ end
45
+
46
+ #
47
+ # Finds the position before the target position.
48
+ # If generated key is invalid(ex: it already exists),
49
+ # a new key is generated until the challenge count reaches the limit.
50
+ # challenge count is 10 by default.
51
+ #
52
+ # @example
53
+ # position = Position.new
54
+ # position.find_position_before(target, challenge: 5)
55
+ #
56
+ # @param target [#send(field)]
57
+ # @param args [Hash] Additional arguments.
58
+ # @option args [Integer] :challenge The number of times to attempt finding a valid position.
59
+ # @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, **args)
61
+ merged_args = { challenge: 10 }.merge(args)
62
+ # when target is nil, try to generate key from the first position
63
+ target_key = target&.send(option.field) || current_first_position
64
+ key = FractionalIndexer.generate_key(next_key: target_key)
65
+ return key if valid?(key)
66
+
67
+ (merged_args[:challenge] || 0).times do |i|
68
+ key = FractionalIndexer.generate_key(prev_key: key, next_key: target_key)
69
+ key += random_fractional
70
+ return key if valid?(key)
71
+ end
72
+
73
+ nil
74
+ rescue FractionalIndexer::Error
75
+ nil
76
+ end
77
+
78
+ # Finds the position between two targets.
79
+ #
80
+ # @param prev_target [#send(field)] The previous target.
81
+ # @param next_target [#send(field)] The next target.
82
+ # @param args [Hash] Additional arguments.
83
+ # @option args [Integer] :challenge The number of times to attempt finding a valid position.
84
+ # @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, **args)
86
+ return find_position_before(next_target, **args) if prev_target.blank?
87
+ return find_position_after(prev_target, **args) if next_target.blank?
88
+
89
+ merged_args = { challenge: 10 }.merge(args)
90
+
91
+ prev_key, next_key = [ prev_target.send(option.field), next_target.send(option.field) ].minmax
92
+ key = FractionalIndexer.generate_key(
93
+ prev_key: prev_key,
94
+ next_key: next_key,
95
+ )
96
+ return key if valid?(key)
97
+
98
+ (merged_args[:challenge] || 0).times do |i|
99
+ key = FractionalIndexer.generate_key(prev_key: key, next_key: next_key)
100
+ key += random_fractional
101
+ return key if valid?(key)
102
+ end
103
+
104
+ nil
105
+ rescue FractionalIndexer::Error
106
+ nil
107
+ end
108
+
109
+ private
110
+
111
+ attr_reader :record, :option
112
+
113
+ def capable?(key)
114
+ option.key_max_size >= key.size
115
+ end
116
+
117
+ def current_first_position
118
+ model.merge(model_scope).minimum(option.field)
119
+ end
120
+
121
+ def current_last_position
122
+ model.merge(model_scope).maximum(option.field)
123
+ end
124
+
125
+ def model
126
+ record.class.base_class
127
+ end
128
+
129
+ # generate a random fractional part
130
+ #
131
+ # @return [String] The random fractional part.
132
+ # @see https://github.com/kazu-2020/fractional_indexer?tab=readme-ov-file#fractional-part
133
+ def random_fractional
134
+ # `fractional` represents the fractional part, but to ensure that the last digit is not zero value (ex: base_62 => '0'), the range is set to [1..].
135
+ FractionalIndexer.configuration.digits[1..].sample
136
+ end
137
+
138
+ def model_scope
139
+ model.where(record.slice(*option.scope))
140
+ end
141
+
142
+ def uniq?(key)
143
+ model.where(option.field => key).merge(model_scope).empty?
144
+ end
145
+
146
+ def valid?(key)
147
+ return false if key.blank?
148
+
149
+ capable?(key) && uniq?(key)
150
+ end
151
+ end
152
+ end
@@ -1,3 +1,3 @@
1
1
  module Narabikae
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.1"
3
3
  end
data/lib/narabikae.rb CHANGED
@@ -1,6 +1,75 @@
1
1
  require "narabikae/version"
2
- require "narabikae/railtie"
2
+
3
+ require "narabikae/active_record_extension"
4
+ require "narabikae/configuration"
5
+ require "narabikae/option"
6
+ require "narabikae/option_store"
7
+ require "narabikae/position"
8
+
9
+ require "fractional_indexer"
10
+ require "active_support"
11
+ require "active_record"
3
12
 
4
13
  module Narabikae
5
- # Your code goes here...
14
+ class Error < StandardError; end
15
+
16
+ @configuration = Narabikae::Configuration.new
17
+
18
+ def self.configure
19
+ yield configuration if block_given?
20
+
21
+ configuration
22
+ end
23
+
24
+ def self.configuration
25
+ @configuration
26
+ end
27
+
28
+ module Extension
29
+ extend ActiveSupport::Concern
30
+
31
+ class_methods do
32
+ def narabikae(field = :position, size:, scope: [])
33
+ option = narabikae_option_store.register!(
34
+ field.to_sym,
35
+ Narabikae::Option.new(field: field, key_max_size: size, scope: scope)
36
+ )
37
+
38
+ before_create do
39
+ extension = Narabikae::ActiveRecordExtension.new(self, option)
40
+ extension.set_position
41
+ end
42
+
43
+ before_update do
44
+ extension = Narabikae::ActiveRecordExtension.new(self, option)
45
+ extension.set_position if extension.auto_set_position?
46
+ end
47
+
48
+ define_method :"move_to_#{field}_after" do |target = nil, **args|
49
+ extension = Narabikae::ActiveRecordExtension.new(self, option)
50
+ extension.move_to_after(target, **args)
51
+ end
52
+
53
+ define_method :"move_to_#{field}_before" do |target = nil, **args|
54
+ extension = Narabikae::ActiveRecordExtension.new(self, option)
55
+ extension.move_to_before(target, **args)
56
+ end
57
+
58
+ define_method :"move_to_#{field}_between" do |prev_target = nil, next_target = nil, **args|
59
+ extension = Narabikae::ActiveRecordExtension.new(self, option)
60
+ extension.move_to_between(prev_target, next_target, **args)
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def narabikae_option_store
67
+ @_narabikae_option_store ||= Narabikae::OptionStore.new
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ ActiveSupport.on_load :active_record do |base|
74
+ base.include Narabikae::Extension
6
75
  end
metadata CHANGED
@@ -1,35 +1,112 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: narabikae
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - matazou
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-10-01 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
- name: rails
13
+ name: fractional_indexer
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
- - - "~>"
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 0.4.0
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
18
24
  - !ruby/object:Gem::Version
19
- version: '7.1'
25
+ version: 0.4.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: activerecord
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
20
30
  - - ">="
21
31
  - !ruby/object:Gem::Version
22
- version: 7.1.3.2
32
+ version: '6.1'
23
33
  type: :runtime
24
34
  prerelease: false
25
35
  version_requirements: !ruby/object:Gem::Requirement
26
36
  requirements:
27
- - - "~>"
37
+ - - ">="
28
38
  - !ruby/object:Gem::Version
29
- version: '7.1'
39
+ version: '6.1'
40
+ - !ruby/object:Gem::Dependency
41
+ name: activesupport
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
30
44
  - - ">="
31
45
  - !ruby/object:Gem::Version
32
- version: 7.1.3.2
46
+ version: '6.1'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '6.1'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rspec-rails
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rubocop-rails-omakase
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: 1.0.0
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: 1.0.0
82
+ - !ruby/object:Gem::Dependency
83
+ name: rubocop-rspec
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: 3.1.0
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: 3.1.0
96
+ - !ruby/object:Gem::Dependency
97
+ name: appraisal
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - '='
101
+ - !ruby/object:Gem::Version
102
+ version: 2.5.0
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - '='
108
+ - !ruby/object:Gem::Version
109
+ version: 2.5.0
33
110
  description: 'provides functionality similar to acts_as_list. However, by managing
34
111
  position using a fractional indexing system, it allows database record updates during
35
112
  reordering to be completed with only a single update (N = 1)!
@@ -44,9 +121,13 @@ files:
44
121
  - MIT-LICENSE
45
122
  - README.md
46
123
  - lib/narabikae.rb
47
- - lib/narabikae/railtie.rb
124
+ - lib/narabikae/active_record_extension.rb
125
+ - lib/narabikae/active_record_handler.rb
126
+ - lib/narabikae/configuration.rb
127
+ - lib/narabikae/option.rb
128
+ - lib/narabikae/option_store.rb
129
+ - lib/narabikae/position.rb
48
130
  - lib/narabikae/version.rb
49
- - lib/tasks/narabikae_tasks.rake
50
131
  homepage: https://github.com/kazu-2020/narabikae
51
132
  licenses:
52
133
  - MIT
@@ -57,7 +138,6 @@ metadata:
57
138
  source_code_uri: https://github.com/kazu-2020/narabikae
58
139
  allowed_push_host: https://rubygems.org/
59
140
  rubygems_mfa_required: 'true'
60
- post_install_message:
61
141
  rdoc_options: []
62
142
  require_paths:
63
143
  - lib
@@ -65,15 +145,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
65
145
  requirements:
66
146
  - - ">="
67
147
  - !ruby/object:Gem::Version
68
- version: '0'
148
+ version: '3.0'
69
149
  required_rubygems_version: !ruby/object:Gem::Requirement
70
150
  requirements:
71
151
  - - ">="
72
152
  - !ruby/object:Gem::Version
73
153
  version: '0'
74
154
  requirements: []
75
- rubygems_version: 3.5.3
76
- signing_key:
155
+ rubygems_version: 3.6.7
77
156
  specification_version: 4
78
157
  summary: provides simple position management and sorting functionality for Active
79
158
  Record in Rails.
@@ -1,4 +0,0 @@
1
- module Narabikae
2
- class Railtie < ::Rails::Railtie
3
- end
4
- end
@@ -1,4 +0,0 @@
1
- # desc "Explaining what the task does"
2
- # task :narabikae do
3
- # # Task goes here
4
- # end