Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Narabikae(Japanese: 並び替え) means "reorder". Like acts_as_list, this gem provides automatic order management and reordering functionality for your records.
One of the key advantages of this gem is its use of the fractional indexing algorithm, 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 🎉.
In your Gemfile
gem "narabikae"
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!
# example
create_table :tasks do |t|
t.string :name
# for MySQL
t.string :position, null: false, limit: 200, charset: 'ascii', collation: 'ascii_bin'
# for PostgreSQL
t.string :position, null: false, limit: 200, collation: 'C'
# for SQLite3
t.string :position, null: false, collation: 'binary'
end
add_index :tasks, :position, unique: true
Set the collation to distinguish between uppercase and lowercase letters.
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.
It is recommended to apply both NOT NULL and UNIQUE constraints.
This ensures data integrity and efficient ordering.
Explicitly set a character limit for the column.
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.
You only need to add one line as shown below!
class Task < ApplicationRecord
narabikae :position, size: 200
# arg1: optional
# . Specify the field you want to use for ordering.
# The default is :position.
#
# size: required
# Used for validation of the internally generated order value.
# This value should be equivalent to
# the limit set in the DB column.
end
Once this is done, the position will be automatically set each time a Task model instance is saved!
Task.create([
{ name: 'task-1' },
{ name: 'task-2' },
{ name: 'task-3' }
])
Task.order(:position).pluck(:name, :position)
# => [["task-1", "a0"], ["task-3", "a1"], ["task-1", "a2"]]
[!NOTE] The position is set using the before_create callback. Therefore, do not define validations such as presence on the attributes managed by this gem!
To insert an element after any specified item, use the move_to_<field>_after
method.
target = Task.create(name: 'target') # pos: 'a0'
tasks = Task.create([
{ name: 'task-1' }, # pos: 'a1'
{ name: 'task-2' } # pos: 'a2'
])
target.move_to_position_after(tasks.last)
# => true
target.position
# => 'a3'
# If no argument is passed, it will be inserted at the end of the list
tasks.first.move_to_position_after
# => true
Task.order(:position).pluck(:name, :position)
# => [["task-2", "a2"], ["target", "a3"], ["task-1", "a4"]]
To insert an element before any specified item, use the move_to_<field>_before
method.
tasks = Task.create([
{ name: 'task-1' }, # pos: 'a0'
{ name: 'task-2' } # pos: 'a1'
])
target = Task.create(name: 'target') # pos: 'a2'
target.move_to_position_before(tasks.first)
target.position
# => 'Zz'
# If no argument is passed, it will be inserted at the start of the list
tasks.last.move_to_position_before
# => true
Task.order(:position).pluck(:name, :position)
# => [["task-2", "Zy"], ["target", "Zz"], ["task-1", "a0"]]
The method you will likely use most often is move_to_<field>_between
, which moves an element between two others!
tasks = Task.create([
{ name: 'task-1' }, # pos: 'a0'
{ name: 'task-2' } # pos: 'a1'
])
target = Task.create(name: 'target') # pos: 'a2'
target.move_to_position_between(tasks.first, tasks.last)
# => true
target.position
# => 'a0V'
# If the first argument is nil, it behaves the same as `move_to_<field>_before`
# ex: target.move_to_position_between(nil, tasks.last)
# If the second argument is nil, it behaves the same as `move_to_<field>_after`
# ex: target.move_to_position_between(tasks.first, nil)
You can use this when you want to manage independent positions within specific scopes, such as foreign keys.
# example
class Course < ApplicationRecord
has_many :chapters, dependent: :destroy
end
class Chapter < ApplicationRecord
belongs_to :course
narabikae :position, size: 100, scope: %i[course_id]
end
course = Course.create
other_course = Course.create
course.chapters.create
other_course.chapters.create
Chapter.pluck(:course_id, :position)
# => [[1, "a0"], [2, "a0"]]
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.
course = Course.create
other_course = Course.create
course.chapters.create
chapter = course.chapters.create # pos: 'a1'
chapter.course = other_course
chapter.save
chapter.position
# => 'a0'
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).
If you want to increase the number of retries, you can use the "challenge" option to control the retry attempts, as shown below:
ticket.move_to_position_between(t1, t2, challenge: 15)
[!NOTE] 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.
Feel free to message me on Github (kazu-2020)
Please wait a moment... 🙏
The gem is available as open source under the terms of the MIT License.
FAQs
Unknown package
We found that narabikae demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.